diff --git a/.gitignore b/.gitignore index c8653840..86726cfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Miscellaneous *.class *.log +*.tmp +*.temp +*.cache *.pyc *.swp .DS_Store @@ -9,6 +12,10 @@ .history .svn/ .gradle/ +**/.cache/ +**/cache/ +**/logs/ +**/log/ # IntelliJ related *.iml @@ -44,6 +51,10 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/android/local.properties +/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +/android/.gradle/ +/android/app/.cxx/ # VSCode .vscode @@ -72,3 +83,42 @@ android/app/.cxx # Claude Code /CLAUDE.md /.claude/*.local.* + +# Harmony temporary/generated +/.codex_tmp/ +/DanXi/.codex_tmp/ +/.tmp_hap_inspect/ +/.tmp_har_inspect/ +/.tmp_ohos_har_module/ +/.tmp*/ +/tmp/ +**/tmp/ +**/.tmp/ +**/.tmp*/ +/.tmp_ohos_har_module/ +/ohos/.cache/ +/ohos/**/build/ +/ohos/**/oh_modules/ +/ohos/**/node_modules/ +/ohos/**/.hvigor/ +/ohos/**/local.properties/ +/Harmony/.hvigor/ +/Harmony/oh_modules/ +/Harmony/**/build/ +/Harmony/signing/* +!/Harmony/signing/ +!/Harmony/signing/README.md +!/Harmony/signing/.gitignore +/har/ +**/*.hap +/.vendor_ohos/* +!/.vendor_ohos/screen_brightness_ohos-2.1.2/ +!/.vendor_ohos/screen_brightness_ohos-2.1.2/** +/.vendor_ohos/**/build/ +/.vendor_ohos/**/oh_modules/ +/.vendor_ohos/**/node_modules/ +/.vendor_ohos/**/.hvigor/ +/.vendor_ohos/**/local.properties +/.vendor_ohos/**/oh-package-lock.json5 +/.vendor_ohos/**/libs/*.har +/Microsoft/ diff --git a/.ohos/.gitignore b/.ohos/.gitignore new file mode 100644 index 00000000..e3264914 --- /dev/null +++ b/.ohos/.gitignore @@ -0,0 +1,20 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +**/BuildProfile.ets +**/oh-package-lock.json5 +/package.json +/package-lock.json + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/**/libapp.so +**/libs/**/libflutter.so +**/libs/**/libvmservice_snapshot.so diff --git a/.ohos/AppScope/app.json5 b/.ohos/AppScope/app.json5 new file mode 100644 index 00000000..08fac823 --- /dev/null +++ b/.ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.dan_xi", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/.ohos/AppScope/resources/base/element/color.json b/.ohos/AppScope/resources/base/element/color.json new file mode 100644 index 00000000..e5a8db4b --- /dev/null +++ b/.ohos/AppScope/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/.ohos/AppScope/resources/base/element/string.json b/.ohos/AppScope/resources/base/element/string.json new file mode 100644 index 00000000..13903de5 --- /dev/null +++ b/.ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "dan_xi" + } + ] +} diff --git a/.ohos/AppScope/resources/base/media/app_icon.png b/.ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 00000000..ce307a88 Binary files /dev/null and b/.ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/.ohos/AppScope/resources/base/profile/main_pages.json b/.ohos/AppScope/resources/base/profile/main_pages.json new file mode 100644 index 00000000..98eee28c --- /dev/null +++ b/.ohos/AppScope/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} \ No newline at end of file diff --git a/.ohos/flutter_module/.gitignore b/.ohos/flutter_module/.gitignore new file mode 100644 index 00000000..121e9711 --- /dev/null +++ b/.ohos/flutter_module/.gitignore @@ -0,0 +1,15 @@ +/node_modules +/oh_modules +/local.properties +/.preview +/build +/.cxx +/.test +**/BuildProfile.ets +**/oh-package-lock.json5 +GeneratedPluginRegistrant.ets + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/**/libapp.so +**/libs/**/libflutter.so +**/libs/**/libvmservice_snapshot.so \ No newline at end of file diff --git a/.ohos/flutter_module/build-profile.json5 b/.ohos/flutter_module/build-profile.json5 new file mode 100644 index 00000000..20c75049 --- /dev/null +++ b/.ohos/flutter_module/build-profile.json5 @@ -0,0 +1,14 @@ +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/.ohos/flutter_module/hvigorfile.ts b/.ohos/flutter_module/hvigorfile.ts new file mode 100644 index 00000000..42187071 --- /dev/null +++ b/.ohos/flutter_module/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/.ohos/flutter_module/index.ets b/.ohos/flutter_module/index.ets new file mode 100644 index 00000000..de8d6a43 --- /dev/null +++ b/.ohos/flutter_module/index.ets @@ -0,0 +1 @@ +export { GeneratedPluginRegistrant } from './src/main/ets/plugins/GeneratedPluginRegistrant' \ No newline at end of file diff --git a/.ohos/flutter_module/oh-package.json5 b/.ohos/flutter_module/oh-package.json5 new file mode 100644 index 00000000..18f11b76 --- /dev/null +++ b/.ohos/flutter_module/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "@ohos/flutter_module", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "author": "", + "license": "OpenValley", + "dependencies": {} +} + diff --git a/.ohos/flutter_module/src/main/module.json5 b/.ohos/flutter_module/src/main/module.json5 new file mode 100644 index 00000000..f66cf4ff --- /dev/null +++ b/.ohos/flutter_module/src/main/module.json5 @@ -0,0 +1,18 @@ +{ + "module": { + "name": "flutter_module", + "type": "har", + "description": "flutter_module", + "mainElement": "", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "installationFree": false, + "abilities": [], + "requestPermissions": [ + {"name" : "ohos.permission.INTERNET"}, + ] + } +} \ No newline at end of file diff --git a/.ohos/hvigor/hvigor-config.json5 b/.ohos/hvigor/hvigor-config.json5 new file mode 100644 index 00000000..ad3ecab4 --- /dev/null +++ b/.ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,6 @@ + +{ + "modelVersion": "5.1.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/.ohos/hvigorconfig.ts b/.ohos/hvigorconfig.ts new file mode 100644 index 00000000..4a8f5234 --- /dev/null +++ b/.ohos/hvigorconfig.ts @@ -0,0 +1,3 @@ +import { injectNativeModules, getFlutterProjectPath } from './include_flutter'; + +injectNativeModules(__dirname, getFlutterProjectPath(), 1) \ No newline at end of file diff --git a/.ohos/hvigorfile.ts b/.ohos/hvigorfile.ts new file mode 100644 index 00000000..7bf56c27 --- /dev/null +++ b/.ohos/hvigorfile.ts @@ -0,0 +1,7 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; +import { flutterHvigorPlugin, getFlutterProjectPath } from './include_flutter'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[flutterHvigorPlugin(getFlutterProjectPath(), 1)] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/.ohos/include_flutter.ts b/.ohos/include_flutter.ts new file mode 100644 index 00000000..54aab5c0 --- /dev/null +++ b/.ohos/include_flutter.ts @@ -0,0 +1,7 @@ +import path from 'path' + +export { flutterHvigorPlugin, injectNativeModules } from 'flutter-hvigor-plugin' + +export function getFlutterProjectPath(): string { + return path.dirname(__dirname) +} \ No newline at end of file diff --git a/.ohos/oh-package.json5 b/.ohos/oh-package.json5 new file mode 100644 index 00000000..07432143 --- /dev/null +++ b/.ohos/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "modelVersion": "5.1.0", + "name": "dan_xi", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {}, + "devDependencies": { + "@ohos/hypium": "1.0.6" + } +} diff --git a/.vendor/flutter_inappwebview_linux/CHANGELOG.md b/.vendor/flutter_inappwebview_linux/CHANGELOG.md new file mode 100644 index 00000000..e57de0b6 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0-beta.1 + +- Initial release. diff --git a/.vendor/flutter_inappwebview_linux/LICENSE b/.vendor/flutter_inappwebview_linux/LICENSE new file mode 100644 index 00000000..6ccd8da4 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Lorenzo Pichilli + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.vendor/flutter_inappwebview_linux/README.md b/.vendor/flutter_inappwebview_linux/README.md new file mode 100644 index 00000000..be3c486d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/README.md @@ -0,0 +1,13 @@ +# flutter\_inappwebview\_linux + +The Linux WPE WebKit implementation of [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview). + +## Usage + +This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +which means you can simply use `flutter_inappwebview` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. \ No newline at end of file diff --git a/.vendor/flutter_inappwebview_linux/WPE_BACKEND.md b/.vendor/flutter_inappwebview_linux/WPE_BACKEND.md new file mode 100644 index 00000000..1c0b07bd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/WPE_BACKEND.md @@ -0,0 +1,594 @@ +# WPE WebKit Backend for flutter_inappwebview_linux + +This document describes how to install and configure WPE WebKit for the flutter_inappwebview Linux plugin. + +## Overview + +This plugin uses WPE WebKit for offscreen web rendering. WPE WebKit is the official WebKit port for embedded systems: + +1. **Designed for headless/offscreen rendering** - No GTK widget hierarchy required +2. **Excellent GPU integration** - Uses DMA-BUF for zero-copy texture sharing with Flutter +3. **Lower memory footprint** - No widget hierarchy overhead +4. **Perfect for embedded systems** - Raspberry Pi, set-top boxes, kiosks, etc. + +The WPE backend exports frames directly as GPU textures via DMA-BUF or SHM buffers. + +## Backend Selection + +The plugin supports two backend APIs, with automatic selection at compile time: + +| Backend | Package | Status | Description | +|---------|---------|--------|-------------| +| **WPEPlatform** | `wpe-platform-2.0` + `wpe-platform-headless-2.0` | **DEFAULT** | Modern API for WPE WebKit 2.40+. Recommended for new installations. | +| **WPEBackend-FDO** | `wpebackend-fdo-1.0` | Legacy Fallback | Used only when WPEPlatform is not available. For older systems. | + +### Backend Detection Logic + +The build system automatically selects the backend: + +1. **If WPEPlatform is found** (`wpe-platform-2.0` and `wpe-platform-headless-2.0`): + - WPEPlatform is used as the default backend + - `HAVE_WPE_PLATFORM=1` is defined + - WPEBackend-FDO is ignored even if available + +2. **If WPEPlatform is NOT found** but WPEBackend-FDO is available: + - WPEBackend-FDO is used as legacy fallback + - `HAVE_WPE_BACKEND_LEGACY=1` is defined + +3. **If neither is found**: Build fails with an error message. + +> **Note:** WPEPlatform and WPEBackend-FDO are **mutually exclusive** at compile time. You cannot use both simultaneously. + +## Installation Options + +You can either: +1. **Use pre-built packages** from your distribution (if available): https://wpewebkit.org/about/get-wpe.html +2. **Build from source** using official tarball releases (recommended for latest features): https://wpewebkit.org/release/ + +### Option 2: Build from Source + +#### Prerequisites + +Install the **required** build dependencies (optional features are listed separately below): + +```bash +# Core build tools +sudo apt-get install -y \ + build-essential cmake ninja-build meson pkg-config \ + ruby ruby-dev python3 python3-pip \ + gperf unifdef + +# GLib (required) +sudo apt-get install -y libglib2.0-dev + +# Networking and security (required) +sudo apt-get install -y \ + libsoup-3.0-dev \ + libssl-dev libgnutls28-dev \ + libsecret-1-dev \ + libgcrypt20-dev libtasn1-dev + +# Graphics and rendering (required) +sudo apt-get install -y \ + libepoxy-dev \ + libegl1-mesa-dev libgles2-mesa-dev \ + libxkbcommon-dev + +# Image and font processing (required) +sudo apt-get install -y \ + libjpeg-dev libpng-dev libwebp-dev \ + libharfbuzz-dev libharfbuzz-icu0 libfreetype6-dev libfontconfig1-dev + +# Text and internationalization (required) +sudo apt-get install -y \ + libicu-dev libxml2-dev \ + libhyphen-dev libenchant-2-dev + +# Media and audio (required for ENABLE_VIDEO and ENABLE_WEB_AUDIO) +sudo apt-get install -y \ + libgstreamer1.0-dev \ + libgstreamer-plugins-base1.0-dev \ + libgstreamer-plugins-bad1.0-dev \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good + +# Database and storage (required) +sudo apt-get install -y libsqlite3-dev + +# WPE libraries (required) +sudo apt-get install -y libwpe-1.0-dev +``` + +> **Note:** Optional dependencies (libjxl, libavif, flite, libdrm, etc.) are listed in the "Installing All Optional Dependencies" section below. Install them for full feature support, or disable the corresponding CMake flags. + +#### Optional Features and Dependencies + +WPE WebKit has many optional features that are **enabled by default**. If you don't have the required dependencies installed, you must either install them or disable the feature via CMake flags. + +##### Features Enabled by Default + +| CMake Flag | Default | Min Version | Dependencies (Debian/Ubuntu) | Description | +|------------|---------|-------------|------------------------------|-------------| +| `USE_JPEGXL` | ON | 0.7.0 | `libjxl-dev` | JPEG XL image format support | +| `USE_AVIF` | ON | 0.9.0 | `libavif-dev` | AVIF image format support | +| `USE_WOFF2` | ON | 1.0.2 | `libwoff-dev` | WOFF2 web font support | +| `USE_LCMS` | ON | — | `liblcms2-dev` | Color management (Little CMS) | +| `USE_ATK` | ON | 2.16.0 | `libatk1.0-dev libatk-bridge2.0-dev` | Accessibility toolkit | +| `USE_GBM` | ON | — | `libgbm-dev` | Generic Buffer Management (GPU) | +| `USE_LIBDRM` | ON | — | `libdrm-dev` | Direct Rendering Manager | +| `USE_LIBBACKTRACE` | ON | — | `libbacktrace-dev` | Stack trace support | +| `USE_SKIA_OPENTYPE_SVG` | ON | — | — | Skia OpenType SVG font support | +| `ENABLE_SPEECH_SYNTHESIS` | ON | — | See speech options below | Text-to-speech support | +| `USE_FLITE` | ON | 2.2 | `flite1-dev` | Flite speech engine (used when speech enabled) | +| `USE_SPIEL` | OFF | — | `libspiel-dev` | Alternative speech engine (LibSpiel) | +| `ENABLE_XSLT` | ON | 1.1.13 | `libxslt1-dev` | XSLT transformation support | +| `ENABLE_INTROSPECTION` | ON | — | `gobject-introspection libgirepository1.0-dev` | GObject introspection | +| `ENABLE_DOCUMENTATION` | ON | — | `pip3 install gi-docgen` | API documentation generation | +| `ENABLE_JOURNALD_LOG` | ON | — | `libsystemd-dev` or `libelogind-dev` | Systemd journal logging | +| `ENABLE_BUBBLEWRAP_SANDBOX` | ON | — | `bubblewrap xdg-dbus-proxy libseccomp-dev` | Process sandboxing (Linux only) | +| `ENABLE_WEBDRIVER` | ON | — | — | WebDriver automation support | +| `ENABLE_PDFJS` | ON | — | — | PDF.js viewer | +| `ENABLE_VIDEO` | ON | — | GStreamer (see prerequisites) | HTML5 video support | +| `ENABLE_WEB_AUDIO` | ON | — | GStreamer (see prerequisites) | Web Audio API support | +| `ENABLE_GAMEPAD` | ON | 0.2.4 | `libmanette-0.2-dev` | Gamepad/controller support | +| `ENABLE_MEDIA_STREAM` | ON | — | GStreamer plugins | Camera/microphone access | +| `USE_GSTREAMER_WEBRTC` | OFF | — | `gstreamer1.0-plugins-bad` | GStreamer-based WebRTC | + +##### Features Disabled by Default (Experimental/Advanced) + +| CMake Flag | Default | Dependencies (Debian/Ubuntu) | Description | +|------------|---------|------------------------------|-------------| +| `ENABLE_WPE_PLATFORM` | OFF | See platform flags below | WPE 2.0 platform abstraction (**required for this plugin**, ⚠️ see note) | +| `ENABLE_WPE_PLATFORM_HEADLESS` | OFF | See platform flags below | Headless platform (**required for this plugin**, ⚠️ see note) | +| `ENABLE_ENCRYPTED_MEDIA` | OFF | Thunder/OCDM | Encrypted Media Extensions (EME/DRM) | +| `ENABLE_WPE_PLATFORM_DRM` | OFF | `libinput-dev libudev-dev libdrm-dev libgbm-dev` | DRM/KMS platform (requires `USE_GBM`) | +| `ENABLE_WPE_PLATFORM_WAYLAND` | OFF | `libwayland-dev wayland-protocols` | Wayland platform | +| `ENABLE_WPE_QT_API` | OFF | Qt5/Qt6 development packages | Qt/QML API bindings | +| `USE_QT6` | OFF | `qt6-base-dev qt6-declarative-dev` | Use Qt6 instead of Qt5 (requires `ENABLE_WPE_PLATFORM`) | +| `ENABLE_WPE_1_1_API` | OFF | — | Build WPE 1.1 API instead of 2.0 | + +> **⚠️ WPEPlatform for this Plugin:** The `ENABLE_WPE_PLATFORM` flags above are for **building WPE WebKit from source**. To use the modern WPEPlatform backend with this Flutter plugin (the default), you must enable `ENABLE_WPE_PLATFORM=ON` and `ENABLE_WPE_PLATFORM_HEADLESS=ON` when building WPE WebKit. If these are disabled, the plugin will fall back to WPEBackend-FDO. + +##### Disabling Optional Features + +To disable a feature, pass `-D=OFF` to cmake. Example: + +```bash +cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$WPE_PREFIX \ + -DPORT=WPE \ + -DUSE_JPEGXL=OFF \ + -DUSE_AVIF=OFF \ + -DENABLE_SPEECH_SYNTHESIS=OFF \ + -DENABLE_DOCUMENTATION=OFF \ + -DENABLE_INTROSPECTION=OFF \ + -DENABLE_WPE_PLATFORM=ON \ + -DENABLE_WPE_PLATFORM_HEADLESS=ON \ + # ... other options +``` + +##### Minimal Build (Disable Most Optional Features) + +For a minimal build without optional dependencies: + +```bash +cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$WPE_PREFIX \ + -DPORT=WPE \ + -DUSE_JPEGXL=OFF \ + -DUSE_AVIF=OFF \ + -DUSE_WOFF2=OFF \ + -DUSE_LCMS=OFF \ + -DUSE_ATK=OFF \ + -DUSE_LIBBACKTRACE=OFF \ + -DENABLE_SPEECH_SYNTHESIS=OFF \ + -DENABLE_DOCUMENTATION=OFF \ + -DENABLE_INTROSPECTION=OFF \ + -DENABLE_JOURNALD_LOG=OFF \ + -DENABLE_BUBBLEWRAP_SANDBOX=OFF \ + -DENABLE_WEBDRIVER=OFF \ + -DENABLE_GAMEPAD=OFF \ + -DUSE_GSTREAMER_WEBRTC=OFF \ + -DENABLE_MINIBROWSER=OFF \ + -DENABLE_WPE_PLATFORM=ON \ + -DENABLE_WPE_PLATFORM_HEADLESS=ON +``` + +##### Feature-Complete Build (WPE 2.0 with All Features) + +For a full-featured WPE 2.0 build with all optional features enabled: + +```bash +cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$WPE_PREFIX \ + -DPORT=WPE \ + -DENABLE_DOCUMENTATION=OFF \ + -DENABLE_INTROSPECTION=ON \ + -DENABLE_BUBBLEWRAP_SANDBOX=ON \ + -DENABLE_WEBDRIVER=ON \ + -DENABLE_MINIBROWSER=OFF \ + -DENABLE_PDFJS=ON \ + -DENABLE_VIDEO=ON \ + -DENABLE_WEB_AUDIO=ON \ + -DENABLE_SPEECH_SYNTHESIS=ON \ + -DENABLE_XSLT=ON \ + -DENABLE_GAMEPAD=ON \ + -DENABLE_JOURNALD_LOG=ON \ + -DUSE_AVIF=ON \ + -DUSE_WOFF2=ON \ + -DUSE_JPEGXL=ON \ + -DUSE_LCMS=ON \ + -DUSE_ATK=ON \ + -DUSE_GBM=ON \ + -DUSE_LIBDRM=ON \ + -DUSE_LIBBACKTRACE=ON \ + -DUSE_FLITE=ON \ + -DUSE_GSTREAMER_WEBRTC=ON \ + -DENABLE_WPE_PLATFORM=ON \ + -DENABLE_WPE_PLATFORM_HEADLESS=ON +``` + +> **Note:** This requires all optional dependencies to be installed (see "Installing All Optional Dependencies" above). + +##### Installing All Optional Dependencies + +To install **all** optional dependencies for a full-featured build: + +```bash +# Image format support +sudo apt-get install -y \ + libjxl-dev \ + libavif-dev \ + libwoff-dev \ + libopenjp2-7-dev + +# Color management +sudo apt-get install -y liblcms2-dev + +# Accessibility +sudo apt-get install -y libatk1.0-dev libatk-bridge2.0-dev + +# Graphics/GPU (required for USE_GBM and USE_LIBDRM) +sudo apt-get install -y libdrm-dev libgbm-dev + +# Debugging +sudo apt-get install -y libbacktrace-dev + +# Speech synthesis (choose one) +sudo apt-get install -y flite1-dev # Flite (default) +# OR: sudo apt-get install -y libspiel-dev # LibSpiel (alternative) + +# XSLT +sudo apt-get install -y libxslt1-dev + +# GObject introspection & documentation +sudo apt-get install -y gobject-introspection libgirepository1.0-dev +pip3 install gi-docgen + +# Systemd logging +sudo apt-get install -y libsystemd-dev + +# Sandboxing +sudo apt-get install -y bubblewrap xdg-dbus-proxy libseccomp-dev + +# Gamepad support +sudo apt-get install -y libmanette-0.2-dev libevdev-dev + +# WebRTC with GStreamer +sudo apt-get install -y gstreamer1.0-plugins-bad + +# WPE Platform DRM (for ENABLE_WPE_PLATFORM_DRM) +sudo apt-get install -y libinput-dev libudev-dev + +# WPE Platform Wayland (for ENABLE_WPE_PLATFORM_WAYLAND) +sudo apt-get install -y libwayland-dev wayland-protocols + +# Qt6 support (for ENABLE_WPE_QT_API with USE_QT6) +# sudo apt-get install -y qt6-base-dev qt6-declarative-dev +``` + +##### Common Build Errors and Fixes + +| Error Message | Solution | +|---------------|----------| +| `libjxl is required for USE_JPEGXL` | Install `libjxl-dev` OR add `-DUSE_JPEGXL=OFF` | +| `libavif 0.9.0 is required for USE_AVIF` | Install `libavif-dev` OR add `-DUSE_AVIF=OFF` | +| `libwoff2dec is required for USE_WOFF2` | Install `libwoff-dev` OR add `-DUSE_WOFF2=OFF` | +| `libcms2 is required for USE_LCMS` | Install `liblcms2-dev` OR add `-DUSE_LCMS=OFF` | +| `atk is required for USE_ATK` | Install `libatk1.0-dev libatk-bridge2.0-dev` OR add `-DUSE_ATK=OFF` | +| `libbacktrace is required for USE_LIBBACKTRACE` | Install `libbacktrace-dev` OR add `-DUSE_LIBBACKTRACE=OFF` | +| `Flite is needed for ENABLE_SPEECH_SYNTHESIS` | Install `flite1-dev` OR add `-DENABLE_SPEECH_SYNTHESIS=OFF` | +| `LibSpiel is needed for ENABLE_SPEECH_SYNTHESIS` | Install `libspiel-dev` OR use Flite instead | +| `libxslt is required for ENABLE_XSLT` | Install `libxslt1-dev` OR add `-DENABLE_XSLT=OFF` | +| `GObjectIntrospection is needed for ENABLE_INTROSPECTION` | Install `gobject-introspection libgirepository1.0-dev` OR add `-DENABLE_INTROSPECTION=OFF` | +| `gi-docgen is needed for ENABLE_DOCUMENTATION` | Run `pip3 install gi-docgen` OR add `-DENABLE_DOCUMENTATION=OFF` | +| `libsystemd or libelogind are needed for ENABLE_JOURNALD_LOG` | Install `libsystemd-dev` OR add `-DENABLE_JOURNALD_LOG=OFF` | +| `GBM is required for USE_GBM` | Install `libgbm-dev` OR add `-DUSE_GBM=OFF` (disables GPU process) | +| `libdrm is required for USE_LIBDRM` | Install `libdrm-dev` OR add `-DUSE_LIBDRM=OFF` | + +#### Build Instructions + +```bash +# Create a working directory +mkdir -p ~/wpe-build && cd ~/wpe-build + +# Set installation prefix (use /usr/local or a custom path) +export WPE_PREFIX=/usr/local + +# === 1. Build libwpe === +wget https://wpewebkit.org/releases/libwpe-1.16.3.tar.xz +tar xf libwpe-1.16.3.tar.xz +cd libwpe-1.16.3 +cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$WPE_PREFIX +ninja -C build +sudo ninja -C build install +cd .. + +# === 2. Build WPEBackend-fdo (OPTIONAL - only for legacy fallback) === +# Skip this step if you enable ENABLE_WPE_PLATFORM in step 3. +# Only needed for older WPE WebKit builds or as a fallback. +wget https://wpewebkit.org/releases/wpebackend-fdo-1.16.1.tar.xz +tar xf wpebackend-fdo-1.16.1.tar.xz +cd wpebackend-fdo-1.16.1 +meson setup build \ + --prefix=$WPE_PREFIX \ + --buildtype=release +ninja -C build +sudo ninja -C build install +cd .. + +# === 3. Build WPE WebKit === +# This is the largest component and takes significant time/resources +wget https://wpewebkit.org/releases/wpewebkit-2.50.4.tar.xz +tar xf wpewebkit-2.50.4.tar.xz +cd wpewebkit-2.50.4 + +# Configure with recommended options for flutter_inappwebview +# See "Optional Features and Dependencies" section above for all flags +cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$WPE_PREFIX \ + -DPORT=WPE \ + -DENABLE_DOCUMENTATION=OFF \ + -DENABLE_INTROSPECTION=OFF \ + -DENABLE_BUBBLEWRAP_SANDBOX=ON \ + -DENABLE_WEBDRIVER=OFF \ + -DENABLE_MINIBROWSER=OFF \ + -DUSE_AVIF=ON \ + -DUSE_WOFF2=ON \ + -DUSE_JPEGXL=ON \ + -DENABLE_WPE_PLATFORM=ON \ + -DENABLE_WPE_PLATFORM_HEADLESS=ON + +# The last two flags enable the modern WPEPlatform backend (recommended). +# If omitted, the plugin will fall back to legacy WPEBackend-FDO. + +# If you're missing optional dependencies, disable them: +# -DUSE_JPEGXL=OFF # if libjxl-dev not installed +# -DUSE_AVIF=OFF # if libavif-dev not installed +# -DUSE_WOFF2=OFF # if libwoff-dev not installed +# -DUSE_LCMS=OFF # if liblcms2-dev not installed +# -DENABLE_SPEECH_SYNTHESIS=OFF # if flite1-dev not installed +# -DUSE_ATK=OFF # if libatk1.0-dev not installed +# -DUSE_LIBBACKTRACE=OFF # if libbacktrace-dev not installed +# -DENABLE_JOURNALD_LOG=OFF # if libsystemd-dev not installed + +# Build (use -j to limit parallelism if you have limited RAM) +# Each WebKit build process uses ~1.5GB RAM +ninja -C build -j$(nproc) + +# Install +sudo ninja -C build install +cd .. + +# === 4. Update library cache === +sudo ldconfig + +# === 5. Create the default backend symlink (OPTIONAL - only for legacy fallback) === +# WPE looks for libWPEBackend-default.so +ARCH=$(uname -m) +if [ -f "$WPE_PREFIX/lib/${ARCH}-linux-gnu/libWPEBackend-fdo-1.0.so.1" ]; then + sudo ln -sf "$WPE_PREFIX/lib/${ARCH}-linux-gnu/libWPEBackend-fdo-1.0.so.1" \ + "$WPE_PREFIX/lib/libWPEBackend-default.so" +elif [ -f "$WPE_PREFIX/lib/aarch64-linux-gnu/libWPEBackend-fdo-1.0.so.1" ]; then + sudo ln -sf "$WPE_PREFIX/lib/aarch64-linux-gnu/libWPEBackend-fdo-1.0.so.1" \ + "$WPE_PREFIX/lib/libWPEBackend-default.so" +elif [ -f "$WPE_PREFIX/lib/x86_64-linux-gnu/libWPEBackend-fdo-1.0.so.1" ]; then + sudo ln -sf "$WPE_PREFIX/lib/x86_64-linux-gnu/libWPEBackend-fdo-1.0.so.1" \ + "$WPE_PREFIX/lib/libWPEBackend-default.so" +elif [ -f "$WPE_PREFIX/lib/libWPEBackend-fdo-1.0.so.1" ]; then + sudo ln -sf "$WPE_PREFIX/lib/libWPEBackend-fdo-1.0.so.1" \ + "$WPE_PREFIX/lib/libWPEBackend-default.so" +fi +``` + +#### Verify Installation + +```bash +# Check pkg-config can find the libraries +pkg-config --modversion wpe-webkit-2.0 +# (OPTIONAL - only for legacy fallback) +pkg-config --modversion wpebackend-fdo-1.0 +pkg-config --modversion wpe-1.0 +``` + +If pkg-config doesn't find the libraries, add the installation path: + +```bash +export PKG_CONFIG_PATH=$WPE_PREFIX/lib/pkgconfig:$WPE_PREFIX/lib/$(uname -m)-linux-gnu/pkgconfig:$PKG_CONFIG_PATH +``` + +## Building the Flutter Plugin + +The plugin automatically detects WPE libraries via pkg-config. When WPE is found, it: + +1. **Compiles with the WPE backend** +2. **Bundles WPE libraries** into the Flutter app's `lib/` directory +3. **Creates necessary symlinks** so the app runs without `LD_LIBRARY_PATH` + +### Build Your App + +```bash +cd your_flutter_app +flutter build linux --release +``` + +During build, you should see messages indicating which backend is being used: + +**With WPEPlatform (default):** +``` +-- flutter_inappwebview_linux: Using WPE WebKit backend +-- flutter_inappwebview_linux: Found wpe-webkit-2.0 (2.50.4) +-- flutter_inappwebview_linux: Found wpe-platform-2.0 (WPEPlatform API - DEFAULT) +-- flutter_inappwebview_linux: Found wpe-platform-headless-2.0 +-- flutter_inappwebview_linux: Will bundle /usr/local/lib/libWPEWebKit-2.0.so.1.6.9 +-- flutter_inappwebview_linux: Will bundle /usr/local/lib/libwpe-1.0.so.1.10.0 +``` + +**With WPEBackend-FDO (legacy fallback):** +``` +-- flutter_inappwebview_linux: Using WPE WebKit backend +-- flutter_inappwebview_linux: Found wpe-webkit-2.0 (2.50.4) +-- flutter_inappwebview_linux: Found wpebackend-fdo-1.0 (Legacy FDO API) +-- flutter_inappwebview_linux: Will bundle /usr/local/lib/libWPEWebKit-2.0.so.1.6.9 +-- flutter_inappwebview_linux: Will bundle /usr/local/lib/libwpe-1.0.so.1.10.0 +-- flutter_inappwebview_linux: Will bundle /usr/local/lib/.../libWPEBackend-fdo-1.0.so.1.11.0 +``` + +### Run Your App + +The built app includes all WPE libraries bundled in the `lib/` directory and can be run directly: + +```bash +# x64 architecture +./build/linux/x64/release/bundle/your_app + +# ARM64 architecture +./build/linux/arm64/release/bundle/your_app +``` + +### What Gets Bundled + +The following files are automatically copied to your app's `lib/` directory: + +**Always bundled:** +- `libWPEWebKit-2.0.so.*` - WPE WebKit library (~160MB) +- `libwpe-1.0.so.*` - libwpe library + +**With WPEPlatform (default):** +- WPEPlatform is built into `libWPEWebKit-2.0.so`, so no additional libraries are needed. + +**With WPEBackend-FDO (legacy):** +- `libWPEBackend-fdo-1.0.so.*` - FDO backend library +- `libWPEBackend-default.so` - Symlink to FDO backend + +## Architecture + +``` +Flutter App + │ + ▼ +flutter_inappwebview Dart layer + │ + ▼ +Method Channel + │ + ▼ +InAppWebView (C++) + │ + ├──► WebKitWebView (WPE WebKit) + │ │ + │ ▼ + │ ┌─────────────────────────────────────┐ + │ │ WPE Backend (compile-time choice) │ + │ ├─────────────────────────────────────┤ + │ │ WPEPlatform (DEFAULT) │ + │ │ - wpe-platform-2.0 │ + │ │ - wpe-platform-headless-2.0 │ + │ │ - Modern API (WPE WebKit 2.40+) │ + │ ├─────────────────────────────────────┤ + │ │ WPEBackend-FDO (LEGACY FALLBACK) │ + │ │ - wpebackend-fdo-1.0 │ + │ │ - For older systems │ + │ └─────────────────────────────────────┘ + │ │ + │ ▼ + └──► Flutter Texture ◄──── SHM Buffer / DMA-BUF +``` + +## Troubleshooting + +### "WPE WebKit not found" + +Ensure pkg-config can find the libraries: + +```bash +# Core WPE WebKit library (required) +pkg-config --cflags --libs wpe-webkit-2.0 +pkg-config --cflags --libs wpe-1.0 + +# WPEPlatform (default backend) - check if available +pkg-config --cflags --libs wpe-platform-2.0 +pkg-config --cflags --libs wpe-platform-headless-2.0 + +# WPEBackend-FDO (legacy fallback) - check if available +pkg-config --cflags --libs wpebackend-fdo-1.0 +``` + +If not found, add the installation prefix: + +```bash +export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/$(uname -m)-linux-gnu/pkgconfig:$PKG_CONFIG_PATH +``` + +### "Failed to load WPEBackend-default.so" + +This error only applies to the **WPEBackend-FDO (legacy)** backend. If you're using WPEPlatform, you won't see this error. + +Create the default backend symlink: + +```bash +sudo ln -sf /usr/local/lib/$(uname -m)-linux-gnu/libWPEBackend-fdo-1.0.so.1 \ + /usr/local/lib/libWPEBackend-default.so +``` + +Or for the bundled app, ensure the symlink exists in the `lib/` directory. + +### Build errors for WPE WebKit + +If you encounter missing dependency errors during `cmake`: + +```bash +# If you see "gi-docgen not found" +pip3 install gi-docgen + +# If you see "unifdef not found" +sudo apt-get install unifdef + +# If you see "gperf not found" +sudo apt-get install gperf + +# If you see "ruby not found" +sudo apt-get install ruby ruby-dev + +# If you see GObject introspection errors +sudo apt-get install gobject-introspection libgirepository1.0-dev +``` + +### Libraries not bundled + +If libraries aren't being bundled, check: + +1. CMake output for "Will bundle" messages +2. Library paths are correct in pkg-config + +## Resources + +- [WPE WebKit Official Site](https://wpewebkit.org/) +- [WPE WebKit API Reference](https://webkitgtk.org/reference/wpe-webkit-2.0/stable/) +- [Release Downloads](https://wpewebkit.org/release/) +- [Release Schedule](https://wpewebkit.org/release/schedule/) diff --git a/.vendor/flutter_inappwebview_linux/analysis_options.yaml b/.vendor/flutter_inappwebview_linux/analysis_options.yaml new file mode 100644 index 00000000..e2d13ab5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/analysis_options.yaml @@ -0,0 +1,16 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + constant_identifier_names: ignore + deprecated_member_use_from_same_package: ignore + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options +analyzer: + errors: + constant_identifier_names: ignore + deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore + unnecessary_cast: ignore + unnecessary_import: ignore diff --git a/.vendor/flutter_inappwebview_linux/example/README.md b/.vendor/flutter_inappwebview_linux/example/README.md new file mode 100644 index 00000000..aed2067a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/README.md @@ -0,0 +1,16 @@ +# flutter_inappwebview_linux_example + +Demonstrates how to use the flutter_inappwebview_linux plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/.vendor/flutter_inappwebview_linux/example/analysis_options.yaml b/.vendor/flutter_inappwebview_linux/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/.vendor/flutter_inappwebview_linux/example/assets/date_input_test.html b/.vendor/flutter_inappwebview_linux/example/assets/date_input_test.html new file mode 100644 index 00000000..abc925a9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/assets/date_input_test.html @@ -0,0 +1,191 @@ + + + + + + Date/Time Input Test + + + +

Choose your colors:

+ +
+ + +
+ +
+ + +
+ + +

📅 Date/Time Input Test

+ +
+

How to use

+ +
+ +
Standard Inputs
+ +
+ + +
Value: 2024-03-15
+
+ +
+ + +
Value: 2024-03-15T14:30
+
+ +
+ + +
Value: 14:30
+
+ +
+ + +
Value: 2024-03
+
+ +
+ + +
Value: 2024-W11
+
+ +
With Constraints
+ +
+ + +
Value: (empty)
+
+ +
Disabled / Readonly (should NOT open picker)
+ +
+ + +
Disabled - should not open picker
+
+ +
+ + +
Readonly - should not open picker
+
+ + + + diff --git a/.vendor/flutter_inappwebview_linux/example/assets/test_page.html b/.vendor/flutter_inappwebview_linux/example/assets/test_page.html new file mode 100644 index 00000000..2384cef7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/assets/test_page.html @@ -0,0 +1,11 @@ + + + + + Local File Test Page + + +

Local File Loaded!

+

This page was loaded via loadFile() from Flutter assets.

+ + diff --git a/.vendor/flutter_inappwebview_linux/example/integration_test/plugin_integration_test.dart b/.vendor/flutter_inappwebview_linux/example/integration_test/plugin_integration_test.dart new file mode 100644 index 00000000..c5269d95 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,43 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://flutter.dev/to/integration-testing + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:flutter_inappwebview_linux/flutter_inappwebview_linux.dart'; +import 'package:flutter_inappwebview_linux/src/in_app_webview/custom_platform_view.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('LinuxInAppWebViewWidget can be created', (WidgetTester tester) async { + LinuxInAppWebViewPlatform.registerWith(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) => LinuxInAppWebViewWidget( + LinuxInAppWebViewWidgetCreationParams( + initialUrlRequest: URLRequest(url: WebUri('https://flutter.dev')), + ), + ).build(context), + ), + ), + ), + ); + + // Wait for the widget to be created + await tester.pump(); + + // Verify that the custom platform view was created + expect(find.byType(CustomPlatformView), findsOneWidget); + }); +} diff --git a/.vendor/flutter_inappwebview_linux/example/lib/main.dart b/.vendor/flutter_inappwebview_linux/example/lib/main.dart new file mode 100644 index 00000000..0b677f49 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/lib/main.dart @@ -0,0 +1,387 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview_linux/flutter_inappwebview_linux.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + LinuxInAppWebViewPlatform.registerWith(); + + runApp(const MaterialApp(home: MyApp())); +} + +/// Event handler for InAppBrowser tests +class TestInAppBrowserEventHandler extends PlatformInAppBrowserEvents { + bool browserCreated = false; + bool browserExited = false; + Completer browserCreatedCompleter = Completer(); + Completer browserExitedCompleter = Completer(); + + @override + void onBrowserCreated() { + debugPrint('[TEST] InAppBrowser: onBrowserCreated fired'); + browserCreated = true; + if (!browserCreatedCompleter.isCompleted) { + browserCreatedCompleter.complete(); + } + } + + @override + void onExit() { + debugPrint('[TEST] InAppBrowser: onExit fired'); + browserExited = true; + if (!browserExitedCompleter.isCompleted) { + browserExitedCompleter.complete(); + } + } + + @override + void onLoadStart(WebUri? url) { + debugPrint('[TEST] InAppBrowser: onLoadStart - $url'); + } + + @override + void onLoadStop(WebUri? url) { + debugPrint('[TEST] InAppBrowser: onLoadStop - $url'); + } + + @override + FutureOr shouldOverrideUrlLoading(navigationAction) { + debugPrint("\n\nOverride ${navigationAction.request.url}\n\n"); + return NavigationActionPolicy.ALLOW; + } +} + +/// Test InAppBrowser implementation +Future testInAppBrowser() async { + debugPrint('[TEST] InAppBrowser: Starting test...'); + + final browser = LinuxInAppBrowser(LinuxInAppBrowserCreationParams()); + final eventHandler = TestInAppBrowserEventHandler(); + browser.eventHandler = eventHandler; + + // Open URL + try { + await browser.openUrlRequest( + urlRequest: URLRequest(url: WebUri('https://flutter.dev')), + settings: InAppBrowserClassSettings( + browserSettings: InAppBrowserSettings( + toolbarTopBackgroundColor: Colors.blue, + presentationStyle: ModalPresentationStyle.POPOVER, + ), + webViewSettings: InAppWebViewSettings( + isInspectable: kDebugMode, + useShouldOverrideUrlLoading: true, + useOnLoadResource: true, + ), + ), + ); + debugPrint('[TEST] InAppBrowser: openUrlRequest completed'); + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: openUrlRequest failed - $e'); + return; + } + + // Wait for browser to be created (with timeout) + try { + await eventHandler.browserCreatedCompleter.future.timeout( + const Duration(seconds: 5), + onTimeout: () { + debugPrint( + '[TEST] ❌ InAppBrowser: Timeout waiting for onBrowserCreated', + ); + }, + ); + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: Error waiting for browser - $e'); + } + + if (!eventHandler.browserCreated) { + debugPrint('[TEST] ❌ InAppBrowser: onBrowserCreated not fired'); + return; + } + + debugPrint('[TEST] ✅ InAppBrowser: Browser created successfully'); + + // Test isHidden + try { + final isHidden = await browser.isHidden(); + debugPrint('[TEST] InAppBrowser: isHidden = $isHidden'); + if (isHidden == false) { + debugPrint( + '[TEST] ✅ InAppBrowser: isHidden returned correct value (false)', + ); + } + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: isHidden failed - $e'); + } + + // Test hide + try { + await browser.hide(); + debugPrint('[TEST] ✅ InAppBrowser: hide() called successfully'); + await Future.delayed(const Duration(milliseconds: 500)); + + final isHiddenAfterHide = await browser.isHidden(); + debugPrint('[TEST] InAppBrowser: isHidden after hide = $isHiddenAfterHide'); + if (isHiddenAfterHide == true) { + debugPrint('[TEST] ✅ InAppBrowser: hide() worked correctly'); + } + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: hide failed - $e'); + } + + // Test show + try { + await browser.show(); + debugPrint('[TEST] ✅ InAppBrowser: show() called successfully'); + await Future.delayed(const Duration(milliseconds: 500)); + + final isHiddenAfterShow = await browser.isHidden(); + debugPrint('[TEST] InAppBrowser: isHidden after show = $isHiddenAfterShow'); + if (isHiddenAfterShow == false) { + debugPrint('[TEST] ✅ InAppBrowser: show() worked correctly'); + } + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: show failed - $e'); + } + + // Test setSettings + try { + await browser.setSettings( + settings: InAppBrowserClassSettings( + browserSettings: InAppBrowserSettings( + toolbarTopBackgroundColor: const Color.fromARGB(255, 100, 100, 100), + ), + ), + ); + debugPrint('[TEST] ✅ InAppBrowser: setSettings() called successfully'); + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: setSettings failed - $e'); + } + + // Test getSettings + try { + final settings = await browser.getSettings(); + debugPrint('[TEST] InAppBrowser: getSettings() = $settings'); + debugPrint('[TEST] ✅ InAppBrowser: getSettings() called successfully'); + } catch (e) { + debugPrint('[TEST] ❌ InAppBrowser: getSettings failed - $e'); + } + + // Wait for browser exit (with timeout) + try { + await eventHandler.browserExitedCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () { + debugPrint('[TEST] ⚠️ InAppBrowser: Timeout waiting for onExit'); + }, + ); + } catch (e) { + debugPrint('[TEST] ⚠️ InAppBrowser: Error waiting for exit - $e'); + } + + if (eventHandler.browserExited) { + debugPrint('[TEST] ✅ InAppBrowser: All tests passed!'); + } else { + debugPrint('[TEST] ⚠️ InAppBrowser: Tests completed but onExit not fired'); + } +} + +/// Test openWithSystemBrowser +Future testOpenWithSystemBrowser() async { + debugPrint('[TEST] openWithSystemBrowser: Starting test...'); + try { + final browser = LinuxInAppBrowser(LinuxInAppBrowserCreationParams()); + await browser.openWithSystemBrowser(url: WebUri('https://flutter.dev')); + debugPrint('[TEST] ✅ openWithSystemBrowser: Command executed successfully'); + } catch (e) { + debugPrint('[TEST] ❌ openWithSystemBrowser: $e'); + } +} + +/// Run all InAppBrowser tests +Future runAllInAppBrowserTests() async { + debugPrint(''); + debugPrint('========================================'); + debugPrint('[TEST] Starting InAppBrowser Test Suite'); + debugPrint('========================================'); + debugPrint(''); + + // Test 1: InAppBrowser + await testInAppBrowser(); + + debugPrint(''); + debugPrint('----------------------------------------'); + debugPrint(''); + + debugPrint(''); + debugPrint('========================================'); + debugPrint('[TEST] InAppBrowser Test Suite Complete'); + debugPrint('========================================'); + debugPrint(''); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final GlobalKey webViewKey = GlobalKey(); + + LinuxInAppWebViewController? webViewController; + late LinuxFindInteractionController findInteractionController; + InAppWebViewSettings settings = InAppWebViewSettings( + isInspectable: kDebugMode, + mediaPlaybackRequiresUserGesture: false, + allowsInlineMediaPlayback: true, + iframeAllow: "camera; microphone", + iframeAllowFullscreen: true, + javaScriptCanOpenWindowsAutomatically: true, + // Register custom scheme for onLoadResourceWithCustomScheme testing + // Note: http/https cannot be overridden - WPE WebKit explicitly prohibits it + resourceCustomSchemes: ['myapp'], + // Enable Intelligent Tracking Prevention (ITP) + itpEnabled: true, + ); + + String url = ""; + double progress = 0; + final urlController = TextEditingController(); + + @override + void initState() { + super.initState(); + findInteractionController = LinuxFindInteractionController( + LinuxFindInteractionControllerCreationParams( + onFindResultReceived: + ( + controller, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting, + ) {}, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("Linux InAppWebView Tests")), + body: SafeArea( + child: Column( + children: [ + TextField( + decoration: const InputDecoration(prefixIcon: Icon(Icons.search)), + controller: urlController, + keyboardType: TextInputType.url, + onSubmitted: (value) { + var url = WebUri(value); + if (url.scheme.isEmpty) { + url = WebUri("https://www.google.com/search?q=$value"); + } + webViewController?.loadUrl(urlRequest: URLRequest(url: url)); + }, + ), + Expanded( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(50.0), + child: LinuxInAppWebViewWidget( + LinuxInAppWebViewWidgetCreationParams( + key: webViewKey, + initialUrlRequest: URLRequest( + url: WebUri( + "https://www.youtube.com/watch?v=d7j6vZHskNY&themeRefresh=1", + ), + ), + // initialFile: "assets/date_input_test.html", + initialSettings: settings, + onWebViewCreated: (controller) async { + webViewController = controller; + + await Future.delayed(Duration(seconds: 2)); + controller.loadUrl( + urlRequest: URLRequest( + url: WebUri('https://flutter.dev'), + ), + ); + debugPrint('[TEST] Loaded flutter.dev'); + + await Future.delayed(Duration(seconds: 2)); + controller.reload(); + debugPrint('[TEST] Reloaded flutter.dev'); + + await Future.delayed(Duration(seconds: 2)); + controller.loadUrl( + urlRequest: URLRequest( + url: WebUri('https://google.com'), + ), + ); + debugPrint('[TEST] Loaded google.com'); + + await Future.delayed(Duration(seconds: 2)); + controller.reload(); + debugPrint('[TEST] Reloaded google.com'); + + await Future.delayed(Duration(seconds: 2)); + controller.goBack(); + debugPrint('[TEST] Should go back to flutter.dev'); + + await Future.delayed(Duration(seconds: 2)); + controller.goBack(); + debugPrint('[TEST] Should go back to YouTube'); + }, + ), + ).build(context), + ), + progress < 1.0 + ? LinearProgressIndicator(value: progress) + : Container(), + ], + ), + ), + OverflowBar( + alignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + child: const Icon(Icons.arrow_back), + onPressed: () { + webViewController?.goBack(); + }, + ), + ElevatedButton( + child: const Icon(Icons.arrow_forward), + onPressed: () { + webViewController?.goForward(); + }, + ), + ElevatedButton( + child: const Icon(Icons.refresh), + onPressed: () { + webViewController?.reload(); + }, + ), + ElevatedButton.icon( + icon: const Icon(Icons.open_in_browser), + label: const Text('Test InAppBrowser'), + onPressed: () { + runAllInAppBrowserTests(); + }, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/.vendor/flutter_inappwebview_linux/example/linux/CMakeLists.txt b/.vendor/flutter_inappwebview_linux/example/linux/CMakeLists.txt new file mode 100644 index 00000000..76fc27a5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/CMakeLists.txt @@ -0,0 +1,175 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flutter_inappwebview_linux_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.pichillilorenzo.flutter_inappwebview_linux") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Enable the test target. +set(include_flutter_inappwebview_linux_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# === WPE Library Symlink Creation === +# Create the necessary soname symlinks so the WPE libraries can find each other at runtime +install(CODE " + # Create symlinks for WPE libraries in the lib directory + set(LIB_DIR \"${INSTALL_BUNDLE_LIB_DIR}\") + + # Function to create soname symlinks for a library + macro(create_lib_symlinks LIB_PATTERN SONAME_BASE) + file(GLOB LIB_FILES \"\${LIB_DIR}/\${LIB_PATTERN}\") + if(LIB_FILES) + list(GET LIB_FILES 0 REAL_LIB) + get_filename_component(REAL_LIB_NAME \"\${REAL_LIB}\" NAME) + + # Create .so.X symlink (e.g., libwpe-1.0.so.1 -> libwpe-1.0.so.1.10.0) + set(SONAME_LINK \"\${LIB_DIR}/\${SONAME_BASE}.so.1\") + if(NOT EXISTS \"\${SONAME_LINK}\") + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink + \"\${REAL_LIB_NAME}\" \"\${SONAME_LINK}\") + message(STATUS \"Created symlink: \${SONAME_LINK} -> \${REAL_LIB_NAME}\") + endif() + + # Create .so symlink (e.g., libwpe-1.0.so -> libwpe-1.0.so.1) + set(SO_LINK \"\${LIB_DIR}/\${SONAME_BASE}.so\") + if(NOT EXISTS \"\${SO_LINK}\") + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink + \"\${SONAME_BASE}.so.1\" \"\${SO_LINK}\") + message(STATUS \"Created symlink: \${SO_LINK} -> \${SONAME_BASE}.so.1\") + endif() + endif() + endmacro() + + # Create symlinks for each WPE library + create_lib_symlinks(\"libWPEWebKit-2.0.so.*.*.*\" \"libWPEWebKit-2.0\") + create_lib_symlinks(\"libwpe-1.0.so.*.*.*\" \"libwpe-1.0\") + create_lib_symlinks(\"libWPEBackend-fdo-1.0.so.*.*.*\" \"libWPEBackend-fdo-1.0\") + + # Create the default backend symlink that WPE expects + set(DEFAULT_BACKEND_LINK \"\${LIB_DIR}/libWPEBackend-default.so\") + if(NOT EXISTS \"\${DEFAULT_BACKEND_LINK}\") + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink + \"libWPEBackend-fdo-1.0.so.1\" \"\${DEFAULT_BACKEND_LINK}\") + message(STATUS \"Created symlink: \${DEFAULT_BACKEND_LINK} -> libWPEBackend-fdo-1.0.so.1\") + endif() + " COMPONENT Runtime) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/.vendor/flutter_inappwebview_linux/example/linux/flutter/CMakeLists.txt b/.vendor/flutter_inappwebview_linux/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.cc b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..5a06516d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_inappwebview_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterInappwebviewLinuxPlugin"); + flutter_inappwebview_linux_plugin_register_with_registrar(flutter_inappwebview_linux_registrar); +} diff --git a/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.h b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugins.cmake b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..e6d369b0 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/.vendor/flutter_inappwebview_linux/example/linux/runner/CMakeLists.txt b/.vendor/flutter_inappwebview_linux/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/.vendor/flutter_inappwebview_linux/example/linux/runner/main.cc b/.vendor/flutter_inappwebview_linux/example/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.cc b/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.cc new file mode 100644 index 00000000..997ca776 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "flutter_inappwebview_linux_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "flutter_inappwebview_linux_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.h b/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/.vendor/flutter_inappwebview_linux/example/pubspec.yaml b/.vendor/flutter_inappwebview_linux/example/pubspec.yaml new file mode 100644 index 00000000..718c864c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/pubspec.yaml @@ -0,0 +1,89 @@ +name: flutter_inappwebview_linux_example +description: "Demonstrates how to use the flutter_inappwebview_linux plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ^3.10.4 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + flutter_inappwebview_linux: + # When depending on this package from a real application you should use: + # flutter_inappwebview_linux: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + flutter_inappwebview_platform_interface: + path: ../../flutter_inappwebview_platform_interface + + path_provider: ^2.1.0 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/.vendor/flutter_inappwebview_linux/example/test/widget_test.dart b/.vendor/flutter_inappwebview_linux/example/test/widget_test.dart new file mode 100644 index 00000000..769ed115 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_inappwebview_linux_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/.vendor/flutter_inappwebview_linux/lib/flutter_inappwebview_linux.dart b/.vendor/flutter_inappwebview_linux/lib/flutter_inappwebview_linux.dart new file mode 100644 index 00000000..867711ef --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/flutter_inappwebview_linux.dart @@ -0,0 +1,3 @@ +library flutter_inappwebview_linux; + +export 'src/main.dart'; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/cookie_manager.dart b/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/cookie_manager.dart new file mode 100644 index 00000000..54113d19 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/cookie_manager.dart @@ -0,0 +1,182 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxCookieManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformCookieManagerCreationParams] for +/// more information. +class LinuxCookieManagerCreationParams + extends PlatformCookieManagerCreationParams { + /// Creates a new [LinuxCookieManagerCreationParams] instance. + const LinuxCookieManagerCreationParams(); + + /// Creates a [LinuxCookieManagerCreationParams] instance based on [PlatformCookieManagerCreationParams]. + factory LinuxCookieManagerCreationParams.fromPlatformCookieManagerCreationParams( + PlatformCookieManagerCreationParams params, + ) { + return const LinuxCookieManagerCreationParams(); + } +} + +/// Implementation of [PlatformCookieManager] for Linux using WebKitGTK. +class LinuxCookieManager extends PlatformCookieManager { + static const MethodChannel _channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_cookiemanager', + ); + + /// Constructs a [LinuxCookieManager]. + LinuxCookieManager(PlatformCookieManagerCreationParams params) + : super.implementation( + params is LinuxCookieManagerCreationParams + ? params + : LinuxCookieManagerCreationParams.fromPlatformCookieManagerCreationParams( + params, + ), + ); + + static final LinuxCookieManager _instance = LinuxCookieManager( + const LinuxCookieManagerCreationParams(), + ); + + /// The [LinuxCookieManager] singleton instance. + static LinuxCookieManager instance() => _instance; + + /// Creates and returns a new [LinuxCookieManager] for static methods. + factory LinuxCookieManager.static() => _instance; + + @override + Future setCookie({ + required WebUri url, + required String name, + required String value, + String path = "/", + String? domain, + int? expiresDate, + int? maxAge, + bool? isSecure, + bool? isHttpOnly, + HTTPCookieSameSitePolicy? sameSite, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController, + }) async { + final Map cookie = { + 'name': name, + 'value': value, + 'path': path, + if (domain != null) 'domain': domain, + if (expiresDate != null) 'expiresDate': expiresDate, + if (maxAge != null) 'maxAge': maxAge, + if (isSecure != null) 'isSecure': isSecure, + if (isHttpOnly != null) 'isHttpOnly': isHttpOnly, + if (sameSite != null) 'sameSite': sameSite.toString().split('.').last, + }; + + final result = await _channel.invokeMethod('setCookie', { + 'url': url.toString(), + 'cookie': cookie, + }); + + return result ?? false; + } + + @override + Future> getCookies({ + required WebUri url, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController, + }) async { + final result = await _channel.invokeMethod>('getCookies', { + 'url': url.toString(), + }); + + if (result == null) { + return []; + } + + return result + .cast>() + .map((cookieMap) => Cookie.fromMap(cookieMap.cast())!) + .toList(); + } + + @override + Future getCookie({ + required WebUri url, + required String name, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController, + }) async { + final result = await _channel.invokeMethod?>( + 'getCookie', + {'url': url.toString(), 'name': name}, + ); + + if (result == null) { + return null; + } + + return Cookie.fromMap(result.cast()); + } + + @override + Future deleteCookie({ + required WebUri url, + required String name, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController, + }) async { + final result = await _channel.invokeMethod('deleteCookie', { + 'url': url.toString(), + 'name': name, + 'path': path, + 'domain': domain ?? '', + }); + + return result ?? false; + } + + @override + Future deleteCookies({ + required WebUri url, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController, + }) async { + final result = await _channel.invokeMethod('deleteCookies', { + 'url': url.toString(), + 'path': path, + 'domain': domain ?? '', + }); + + return result ?? false; + } + + @override + Future deleteAllCookies() async { + final result = await _channel.invokeMethod('deleteAllCookies'); + return result ?? false; + } + + @override + Future> getAllCookies() async { + final result = await _channel.invokeMethod>('getAllCookies'); + + if (result == null) { + return []; + } + + return result + .cast>() + .map((cookieMap) => Cookie.fromMap(cookieMap.cast())!) + .toList(); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/main.dart new file mode 100644 index 00000000..e94e06a6 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/cookie_manager/main.dart @@ -0,0 +1 @@ +export 'cookie_manager.dart'; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/find_interaction_controller.dart b/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/find_interaction_controller.dart new file mode 100644 index 00000000..fedd1181 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/find_interaction_controller.dart @@ -0,0 +1,149 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxFindInteractionController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformFindInteractionControllerCreationParams] for +/// more information. +@immutable +class LinuxFindInteractionControllerCreationParams + extends PlatformFindInteractionControllerCreationParams { + /// Creates a new [LinuxFindInteractionControllerCreationParams] instance. + const LinuxFindInteractionControllerCreationParams({ + super.onFindResultReceived, + }); + + /// Creates a [LinuxFindInteractionControllerCreationParams] instance based on [PlatformFindInteractionControllerCreationParams]. + factory LinuxFindInteractionControllerCreationParams.fromPlatformFindInteractionControllerCreationParams( + PlatformFindInteractionControllerCreationParams params, + ) { + return LinuxFindInteractionControllerCreationParams( + onFindResultReceived: params.onFindResultReceived, + ); + } +} + +/// Implementation of [PlatformFindInteractionController] for Linux. +class LinuxFindInteractionController extends PlatformFindInteractionController + with ChannelController { + /// Creates a new [LinuxFindInteractionController]. + LinuxFindInteractionController( + PlatformFindInteractionControllerCreationParams params, + ) : super.implementation( + params is LinuxFindInteractionControllerCreationParams + ? params + : LinuxFindInteractionControllerCreationParams.fromPlatformFindInteractionControllerCreationParams( + params, + ), + ); + + static final LinuxFindInteractionController _staticValue = + LinuxFindInteractionController( + LinuxFindInteractionControllerCreationParams(), + ); + + /// Provide static access. + factory LinuxFindInteractionController.static() { + return _staticValue; + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + debugLoggingSettings: + PlatformFindInteractionController.debugLoggingSettings, + method: method, + args: args, + ); + } + + Future _handleMethod(MethodCall call) async { + _debugLog(call.method, call.arguments); + + switch (call.method) { + case "onFindResultReceived": + if (onFindResultReceived != null) { + int activeMatchOrdinal = call.arguments["activeMatchOrdinal"]; + int numberOfMatches = call.arguments["numberOfMatches"]; + bool isDoneCounting = call.arguments["isDoneCounting"]; + onFindResultReceived!( + this, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting, + ); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findAll} + @override + Future findAll({String? find}) async { + Map args = {}; + args.putIfAbsent('find', () => find); + await channel?.invokeMethod('findAll', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findNext} + @override + Future findNext({bool forward = true}) async { + Map args = {}; + args.putIfAbsent('forward', () => forward); + await channel?.invokeMethod('findNext', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.clearMatches} + @override + Future clearMatches() async { + Map args = {}; + await channel?.invokeMethod('clearMatches', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.setSearchText} + @override + Future setSearchText(String? searchText) async { + Map args = {}; + args.putIfAbsent('searchText', () => searchText); + await channel?.invokeMethod('setSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getSearchText} + @override + Future getSearchText() async { + Map args = {}; + return await channel?.invokeMethod('getSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getActiveFindSession} + @override + Future getActiveFindSession() async { + Map args = {}; + Map? result = (await channel?.invokeMethod( + 'getActiveFindSession', + args, + ))?.cast(); + return FindSession.fromMap(result); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.dispose} + @override + void dispose({bool isKeepAlive = false}) { + disposeChannel(removeMethodCallHandler: !isKeepAlive); + } +} + +extension InternalFindInteractionController on LinuxFindInteractionController { + void init(dynamic id) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id', + ); + handler = _handleMethod; + initMethodCallHandler(); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/main.dart new file mode 100644 index 00000000..a7adaacf --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/find_interaction/main.dart @@ -0,0 +1,2 @@ +export 'find_interaction_controller.dart' + hide InternalFindInteractionController; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/http_auth_credentials_database.dart b/.vendor/flutter_inappwebview_linux/lib/src/http_auth_credentials_database.dart new file mode 100644 index 00000000..674adb47 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/http_auth_credentials_database.dart @@ -0,0 +1,180 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxHttpAuthCredentialDatabase]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformHttpAuthCredentialDatabaseCreationParams] for +/// more information. +@immutable +class LinuxHttpAuthCredentialDatabaseCreationParams + extends PlatformHttpAuthCredentialDatabaseCreationParams { + /// Creates a new [LinuxHttpAuthCredentialDatabaseCreationParams] instance. + const LinuxHttpAuthCredentialDatabaseCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) : super(); + + /// Creates a [LinuxHttpAuthCredentialDatabaseCreationParams] instance based on [PlatformHttpAuthCredentialDatabaseCreationParams]. + factory LinuxHttpAuthCredentialDatabaseCreationParams.fromPlatformHttpAuthCredentialDatabaseCreationParams( + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) { + return LinuxHttpAuthCredentialDatabaseCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHttpAuthCredentialDatabase} +/// +/// This implementation delegates to native C++ code which uses libsecret for secure storage. +/// Passwords are stored in the system keyring (gnome-keyring, KDE Wallet, etc.). +/// A JSON index file at ~/.local/share/flutter_inappwebview//credential_index.json +/// is used to enumerate stored credentials (contains only usernames, not passwords). +/// +/// The native `appId` is resolved from `GApplication.application_id` when available. +/// If it is null/empty, it falls back to the executable filename (from `/proc/self/exe`), sanitized. +class LinuxHttpAuthCredentialDatabase + extends PlatformHttpAuthCredentialDatabase { + static const MethodChannel _channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_credential_database', + ); + + /// Creates a new [LinuxHttpAuthCredentialDatabase]. + LinuxHttpAuthCredentialDatabase( + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) : super.implementation( + params is LinuxHttpAuthCredentialDatabaseCreationParams + ? params + : LinuxHttpAuthCredentialDatabaseCreationParams.fromPlatformHttpAuthCredentialDatabaseCreationParams( + params, + ), + ); + + static LinuxHttpAuthCredentialDatabase? _instance; + + /// Gets the database shared instance. + static LinuxHttpAuthCredentialDatabase instance() { + return (_instance != null) ? _instance! : _init(); + } + + static LinuxHttpAuthCredentialDatabase _init() { + _instance = LinuxHttpAuthCredentialDatabase( + LinuxHttpAuthCredentialDatabaseCreationParams( + const PlatformHttpAuthCredentialDatabaseCreationParams(), + ), + ); + return _instance!; + } + + static final LinuxHttpAuthCredentialDatabase _staticValue = + LinuxHttpAuthCredentialDatabase( + LinuxHttpAuthCredentialDatabaseCreationParams( + const PlatformHttpAuthCredentialDatabaseCreationParams(), + ), + ); + + factory LinuxHttpAuthCredentialDatabase.static() { + return _staticValue; + } + + @override + Future> + getAllAuthCredentials() async { + final List allCredentials = + await _channel.invokeMethod('getAllAuthCredentials', {}) ?? []; + + List result = []; + + for (final Map map in allCredentials) { + final element = URLProtectionSpaceHttpAuthCredentials.fromMap( + map.cast(), + ); + if (element != null) { + result.add(element); + } + } + return result; + } + + @override + Future> getHttpAuthCredentials({ + required URLProtectionSpace protectionSpace, + }) async { + final Map args = { + 'host': protectionSpace.host, + 'protocol': protectionSpace.protocol, + 'realm': protectionSpace.realm, + 'port': protectionSpace.port, + }; + + final List credentialList = + await _channel.invokeMethod('getHttpAuthCredentials', args) ?? []; + + List credentials = []; + for (final Map map in credentialList) { + final credential = URLCredential.fromMap(map.cast()); + if (credential != null) { + credentials.add(credential); + } + } + return credentials; + } + + @override + Future setHttpAuthCredential({ + required URLProtectionSpace protectionSpace, + required URLCredential credential, + }) async { + final Map args = { + 'host': protectionSpace.host, + 'protocol': protectionSpace.protocol, + 'realm': protectionSpace.realm, + 'port': protectionSpace.port, + 'username': credential.username, + 'password': credential.password, + }; + await _channel.invokeMethod('setHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredential({ + required URLProtectionSpace protectionSpace, + required URLCredential credential, + }) async { + final Map args = { + 'host': protectionSpace.host, + 'protocol': protectionSpace.protocol, + 'realm': protectionSpace.realm, + 'port': protectionSpace.port, + 'username': credential.username, + 'password': credential.password, + }; + await _channel.invokeMethod('removeHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredentials({ + required URLProtectionSpace protectionSpace, + }) async { + final Map args = { + 'host': protectionSpace.host, + 'protocol': protectionSpace.protocol, + 'realm': protectionSpace.realm, + 'port': protectionSpace.port, + }; + await _channel.invokeMethod('removeHttpAuthCredentials', args); + } + + @override + Future clearAllAuthCredentials() async { + await _channel.invokeMethod('clearAllAuthCredentials', {}); + } + + /// Disposes of any resources used by this database. + /// Nothing to dispose - native side manages its own lifecycle. + void dispose() { + // Nothing to dispose + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/in_app_browser.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/in_app_browser.dart new file mode 100644 index 00000000..6472495f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/in_app_browser.dart @@ -0,0 +1,413 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../find_interaction/find_interaction_controller.dart'; +import '../in_app_webview/in_app_webview_controller.dart'; +import '../webview_environment/webview_environment.dart'; + +/// Object specifying creation parameters for creating a [LinuxInAppBrowser]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformInAppBrowserCreationParams] for +/// more information. +class LinuxInAppBrowserCreationParams + extends PlatformInAppBrowserCreationParams { + /// Creates a new [LinuxInAppBrowserCreationParams] instance. + LinuxInAppBrowserCreationParams({ + super.contextMenu, + super.pullToRefreshController, + this.findInteractionController, + super.initialUserScripts, + super.windowId, + this.webViewEnvironment, + }); + + /// Creates a [LinuxInAppBrowserCreationParams] instance based on [PlatformInAppBrowserCreationParams]. + factory LinuxInAppBrowserCreationParams.fromPlatformInAppBrowserCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformInAppBrowserCreationParams params, + ) { + return LinuxInAppBrowserCreationParams( + contextMenu: params.contextMenu, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: + params.findInteractionController as LinuxFindInteractionController?, + initialUserScripts: params.initialUserScripts, + windowId: params.windowId, + webViewEnvironment: + params.webViewEnvironment as LinuxWebViewEnvironment?, + ); + } + + @override + final LinuxFindInteractionController? findInteractionController; + + @override + final LinuxWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} +class LinuxInAppBrowser extends PlatformInAppBrowser with ChannelController { + @override + final String id = IdGenerator.generate(); + + /// Constructs a [LinuxInAppBrowser]. + LinuxInAppBrowser(PlatformInAppBrowserCreationParams params) + : super.implementation( + params is LinuxInAppBrowserCreationParams + ? params + : LinuxInAppBrowserCreationParams.fromPlatformInAppBrowserCreationParams( + params, + ), + ) { + _contextMenu = params.contextMenu; + } + + static final LinuxInAppBrowser _staticValue = LinuxInAppBrowser( + LinuxInAppBrowserCreationParams(), + ); + + /// Provide static access. + factory LinuxInAppBrowser.static() { + return _staticValue; + } + + LinuxInAppBrowserCreationParams get _linuxParams => + params as LinuxInAppBrowserCreationParams; + + static const MethodChannel _staticChannel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappbrowser', + ); + + ContextMenu? _contextMenu; + + @override + ContextMenu? get contextMenu => _contextMenu; + + Map _menuItems = HashMap(); + bool _isOpened = false; + LinuxInAppWebViewController? _webViewController; + + @override + LinuxInAppWebViewController? get webViewController { + return _isOpened ? _webViewController : null; + } + + _init() { + channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); + handler = _handleMethod; + initMethodCallHandler(); + + _webViewController = LinuxInAppWebViewController.fromInAppBrowser( + LinuxInAppWebViewControllerCreationParams(id: id), + channel!, + this, + this.initialUserScripts, + ); + _linuxParams.findInteractionController?.init(id); + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + id: id, + debugLoggingSettings: PlatformInAppBrowser.debugLoggingSettings, + method: method, + args: args, + ); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onBrowserCreated": + _debugLog(call.method, call.arguments); + eventHandler?.onBrowserCreated(); + break; + case "onMenuItemClicked": + _debugLog(call.method, call.arguments); + int id = call.arguments["id"].toInt(); + if (this._menuItems[id] != null) { + if (this._menuItems[id]?.onClick != null) { + this._menuItems[id]?.onClick!(); + } + } + break; + case "onMainWindowWillClose": + _debugLog(call.method, call.arguments); + eventHandler?.onMainWindowWillClose(); + break; + case "onExit": + _debugLog(call.method, call.arguments); + _isOpened = false; + final onExit = eventHandler?.onExit; + dispose(); + onExit?.call(); + break; + default: + return _webViewController?.handleMethod(call); + } + } + + Map _prepareOpenRequest({ + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings, + }) { + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; + _init(); + + var initialSettings = + settings?.toMap() ?? + options?.toMap() ?? + InAppBrowserClassSettings().toMap(); + + Map pullToRefreshSettings = + pullToRefreshController?.settings.toMap() ?? + pullToRefreshController?.options.toMap() ?? + PullToRefreshSettings(enabled: false).toMap(); + + List> menuItemList = []; + _menuItems.forEach((key, value) { + menuItemList.add(value.toMap()); + }); + + Map args = {}; + args.putIfAbsent('id', () => id); + args.putIfAbsent('settings', () => initialSettings); + args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); + args.putIfAbsent('windowId', () => windowId); + args.putIfAbsent( + 'initialUserScripts', + () => initialUserScripts?.map((e) => e.toMap()).toList() ?? [], + ); + args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); + args.putIfAbsent('menuItems', () => menuItemList); + args.putIfAbsent( + 'webViewEnvironmentId', + () => _linuxParams.webViewEnvironment?.id, + ); + return args; + } + + @override + Future openUrlRequest({ + required URLRequest urlRequest, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings, + }) async { + assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); + + Map args = _prepareOpenRequest( + options: options, + settings: settings, + ); + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openFile({ + required String assetFilePath, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings, + }) async { + assert(assetFilePath.isNotEmpty); + + Map args = _prepareOpenRequest( + options: options, + settings: settings, + ); + args.putIfAbsent('assetFilePath', () => assetFilePath); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openData({ + required String data, + String mimeType = "text/html", + String encoding = "utf8", + WebUri? baseUrl, + @Deprecated("Use historyUrl instead") Uri? androidHistoryUrl, + WebUri? historyUrl, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings, + }) async { + Map args = _prepareOpenRequest( + options: options, + settings: settings, + ); + args.putIfAbsent('data', () => data); + args.putIfAbsent('mimeType', () => mimeType); + args.putIfAbsent('encoding', () => encoding); + args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank"); + args.putIfAbsent( + 'historyUrl', + () => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank", + ); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openWithSystemBrowser({required WebUri url}) async { + assert(url.toString().isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + return await _staticChannel.invokeMethod('openWithSystemBrowser', args); + } + + @override + void addMenuItem(InAppBrowserMenuItem menuItem) { + _menuItems[menuItem.id] = menuItem; + } + + @override + void addMenuItems(List menuItems) { + menuItems.forEach((menuItem) { + _menuItems[menuItem.id] = menuItem; + }); + } + + @override + bool removeMenuItem(InAppBrowserMenuItem menuItem) { + return _menuItems.remove(menuItem.id) != null; + } + + @override + void removeMenuItems(List menuItems) { + for (final menuItem in menuItems) { + removeMenuItem(menuItem); + } + } + + @override + void removeAllMenuItem() { + _menuItems.clear(); + } + + @override + bool hasMenuItem(InAppBrowserMenuItem menuItem) { + return _menuItems.containsKey(menuItem.id); + } + + @override + Future show() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('show', args); + } + + @override + Future hide() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('hide', args); + } + + @override + Future close() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('close', args); + } + + @override + Future isHidden() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + return await channel?.invokeMethod('isHidden', args) ?? false; + } + + @override + @Deprecated('Use setSettings instead') + Future setOptions({required InAppBrowserClassOptions options}) async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + args.putIfAbsent('settings', () => options.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + @Deprecated('Use getSettings instead') + Future getOptions() async { + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; + + Map? options = await channel?.invokeMethod( + 'getSettings', + args, + ); + if (options != null) { + options = options.cast(); + return InAppBrowserClassOptions.fromMap(options as Map); + } + + return null; + } + + @override + Future setSettings({ + required InAppBrowserClassSettings settings, + }) async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + args.putIfAbsent('settings', () => settings.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + Future getSettings() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + + Map? settings = await channel?.invokeMethod( + 'getSettings', + args, + ); + if (settings != null) { + settings = settings.cast(); + return InAppBrowserClassSettings.fromMap( + settings as Map, + ); + } + + return null; + } + + @override + bool isOpened() { + return this._isOpened; + } + + @override + @mustCallSuper + void dispose() { + super.dispose(); + disposeChannel(); + _webViewController?.dispose(); + _webViewController = null; + pullToRefreshController?.dispose(); + findInteractionController?.dispose(); + } +} + +extension InternalInAppBrowser on LinuxInAppBrowser { + void setContextMenu(ContextMenu? contextMenu) { + _contextMenu = contextMenu; + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/main.dart new file mode 100644 index 00000000..e11eb8b1 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_browser/main.dart @@ -0,0 +1 @@ +export 'in_app_browser.dart' hide InternalInAppBrowser; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/_static_channel.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/_static_channel.dart new file mode 100644 index 00000000..a02f01ec --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/_static_channel.dart @@ -0,0 +1,5 @@ +import 'package:flutter/services.dart'; + +const IN_APP_WEBVIEW_STATIC_CHANNEL = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_manager', +); diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/custom_platform_view.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/custom_platform_view.dart new file mode 100644 index 00000000..5ce9b2d2 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/custom_platform_view.dart @@ -0,0 +1,686 @@ +import 'package:flutter/services.dart'; +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import '_static_channel.dart'; +import 'key_mappings.dart'; + +const Map _cursors = { + // CSS cursor names (used by WebKit and GDK) + 'none': SystemMouseCursors.none, + 'default': SystemMouseCursors.basic, + 'pointer': SystemMouseCursors.click, + 'text': SystemMouseCursors.text, + 'vertical-text': SystemMouseCursors.verticalText, + 'wait': SystemMouseCursors.wait, + 'progress': SystemMouseCursors.progress, + 'help': SystemMouseCursors.help, + 'crosshair': SystemMouseCursors.precise, + 'move': SystemMouseCursors.move, + 'all-scroll': SystemMouseCursors.allScroll, + 'grab': SystemMouseCursors.grab, + 'grabbing': SystemMouseCursors.grabbing, + 'not-allowed': SystemMouseCursors.forbidden, + 'no-drop': SystemMouseCursors.noDrop, + 'context-menu': SystemMouseCursors.contextMenu, + 'cell': SystemMouseCursors.cell, + 'copy': SystemMouseCursors.copy, + 'alias': SystemMouseCursors.alias, + 'col-resize': SystemMouseCursors.resizeColumn, + 'row-resize': SystemMouseCursors.resizeRow, + 'n-resize': SystemMouseCursors.resizeUp, + 's-resize': SystemMouseCursors.resizeDown, + 'e-resize': SystemMouseCursors.resizeRight, + 'w-resize': SystemMouseCursors.resizeLeft, + 'ns-resize': SystemMouseCursors.resizeUpDown, + 'ew-resize': SystemMouseCursors.resizeLeftRight, + 'ne-resize': SystemMouseCursors.resizeUpRight, + 'nw-resize': SystemMouseCursors.resizeUpLeft, + 'se-resize': SystemMouseCursors.resizeDownRight, + 'sw-resize': SystemMouseCursors.resizeDownLeft, + 'nesw-resize': SystemMouseCursors.resizeUpRightDownLeft, + 'nwse-resize': SystemMouseCursors.resizeUpLeftDownRight, + 'zoom-in': SystemMouseCursors.zoomIn, + 'zoom-out': SystemMouseCursors.zoomOut, + // Legacy internal names (for backward compatibility) + 'basic': SystemMouseCursors.basic, + 'click': SystemMouseCursors.click, + 'forbidden': SystemMouseCursors.forbidden, + 'contextMenu': SystemMouseCursors.contextMenu, + 'verticalText': SystemMouseCursors.verticalText, + 'precise': SystemMouseCursors.precise, + 'noDrop': SystemMouseCursors.noDrop, + 'disappearing': SystemMouseCursors.disappearing, + 'allScroll': SystemMouseCursors.allScroll, + 'resizeLeftRight': SystemMouseCursors.resizeLeftRight, + 'resizeUpDown': SystemMouseCursors.resizeUpDown, + 'resizeUpLeftDownRight': SystemMouseCursors.resizeUpLeftDownRight, + 'resizeUpRightDownLeft': SystemMouseCursors.resizeUpRightDownLeft, + 'resizeUp': SystemMouseCursors.resizeUp, + 'resizeDown': SystemMouseCursors.resizeDown, + 'resizeLeft': SystemMouseCursors.resizeLeft, + 'resizeRight': SystemMouseCursors.resizeRight, + 'resizeUpLeft': SystemMouseCursors.resizeUpLeft, + 'resizeUpRight': SystemMouseCursors.resizeUpRight, + 'resizeDownLeft': SystemMouseCursors.resizeDownLeft, + 'resizeDownRight': SystemMouseCursors.resizeDownRight, + 'resizeColumn': SystemMouseCursors.resizeColumn, + 'resizeRow': SystemMouseCursors.resizeRow, + 'zoomIn': SystemMouseCursors.zoomIn, + 'zoomOut': SystemMouseCursors.zoomOut, +}; + +SystemMouseCursor _getCursorByName(String name) => + _cursors[name] ?? SystemMouseCursors.basic; + +/// Pointer button type +enum PointerButton { none, primary, secondary, tertiary } + +/// Pointer Event kind +enum InAppWebViewPointerEventKind { + activate, + down, + enter, + leave, + up, + update, + cancel, +} + +/// Attempts to translate a button constant such as [kPrimaryMouseButton] +/// to a [PointerButton] +PointerButton _getButton(int value) { + switch (value) { + case kPrimaryMouseButton: + return PointerButton.primary; + case kSecondaryMouseButton: + return PointerButton.secondary; + case kTertiaryButton: + return PointerButton.tertiary; + default: + return PointerButton.none; + } +} + +const MethodChannel _pluginChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; + +class CustomFlutterViewControllerValue { + const CustomFlutterViewControllerValue({required this.isInitialized}); + + final bool isInitialized; + + CustomFlutterViewControllerValue copyWith({bool? isInitialized}) { + return CustomFlutterViewControllerValue( + isInitialized: isInitialized ?? this.isInitialized, + ); + } + + CustomFlutterViewControllerValue.uninitialized() : this(isInitialized: false); +} + +/// Controls a WebView and provides streams for various change events. +class CustomPlatformViewController + extends ValueNotifier { + Completer _creatingCompleter = Completer(); + int _textureId = 0; + bool _isDisposed = false; + + Future get ready => _creatingCompleter.future; + + late MethodChannel _methodChannel; + late EventChannel _eventChannel; + StreamSubscription? _eventStreamSubscription; + + final StreamController _cursorStreamController = + StreamController.broadcast(); + + /// A stream reflecting the current cursor style. + Stream get _cursor => _cursorStreamController.stream; + + CustomPlatformViewController() + : super(CustomFlutterViewControllerValue.uninitialized()); + + /// Initializes the underlying platform view. + Future initialize({ + Function(int id)? onPlatformViewCreated, + dynamic arguments, + }) async { + if (_isDisposed) { + return; + } + _textureId = (await _pluginChannel.invokeMethod( + 'createInAppWebView', + arguments, + ))!; + + _methodChannel = MethodChannel( + 'com.pichillilorenzo/custom_platform_view_$_textureId', + ); + _eventChannel = EventChannel( + 'com.pichillilorenzo/custom_platform_view_${_textureId}_events', + ); + _eventStreamSubscription = _eventChannel.receiveBroadcastStream().listen(( + event, + ) { + final map = event as Map; + switch (map['type']) { + case 'cursorChanged': + _cursorStreamController.add(_getCursorByName(map['value'])); + break; + } + }); + + _methodChannel.setMethodCallHandler((call) { + throw MissingPluginException('Unknown method ${call.method}'); + }); + + value = value.copyWith(isInitialized: true); + + _creatingCompleter.complete(); + + onPlatformViewCreated?.call(_textureId); + } + + @override + Future dispose() async { + await _creatingCompleter.future; + if (!_isDisposed) { + _isDisposed = true; + await _eventStreamSubscription?.cancel(); + await _pluginChannel.invokeMethod('dispose', {"id": _textureId}); + } + super.dispose(); + } + + /// Sets the surface size to the provided [size]. + Future _setSize(Size size, double scaleFactor) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setSize', [ + size.width, + size.height, + scaleFactor, + ]); + } + + /// Sets the texture offset (position within the Flutter window). + Future _setTextureOffset(Offset offset) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setTextureOffset', [ + offset.dx, + offset.dy, + ]); + } + + /// Moves the virtual cursor to [position]. + Future _setCursorPos(Offset position) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setCursorPos', [ + position.dx, + position.dy, + ]); + } + + /// Indicates whether the specified [button] is currently down. + Future _setPointerButtonState( + InAppWebViewPointerEventKind kind, + PointerButton button, + ) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerButton', { + 'kind': kind.index, + 'button': button.index, + 'clickCount': 1, + }); + } + + /// Indicates whether the specified [button] is currently down with click count. + Future _setPointerButtonStateWithClickCount( + InAppWebViewPointerEventKind kind, + PointerButton button, + int clickCount, + ) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerButton', { + 'kind': kind.index, + 'button': button.index, + 'clickCount': clickCount, + }); + } + + /// Sets the horizontal and vertical scroll delta. + Future _setScrollDelta(double dx, double dy) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setScrollDelta', [dx, dy]); + } + + /// Sends a key event to the webview. + Future _sendKeyEvent( + int type, + int keyCode, + int scanCode, + int modifiers, + String? characters, + ) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('sendKeyEvent', { + 'type': type, // 0=press, 1=release + 'keyCode': keyCode, + 'scanCode': scanCode, + 'modifiers': modifiers, + 'characters': characters, + }); + } + + /// Sends a touch event to the webview. + /// [type]: 0=down, 1=up, 2=move, 3=cancel + /// [touchPoints]: List of touch point maps with {id, x, y, type} + Future _sendTouchEvent( + int type, + int id, + double x, + double y, + List> touchPoints, + ) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('sendTouchEvent', { + 'type': type, + 'id': id, + 'x': x, + 'y': y, + 'touchPoints': touchPoints, + }); + } + + /// Sets the focus state of the webview. + /// This is called when the Flutter widget gains or loses focus. + Future _setFocused(bool focused) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setFocused', focused); + } +} + +class CustomPlatformView extends StatefulWidget { + /// An optional scale factor. Defaults to [FlutterView.devicePixelRatio] for + /// rendering in native resolution. + /// Setting this to 1.0 will disable high-DPI support. + final double? scaleFactor; + + /// The [FilterQuality] used for scaling the texture's contents. + /// Defaults to [FilterQuality.none] as this renders in native resolution. + final FilterQuality filterQuality; + + final dynamic creationParams; + + final Function(int id)? onPlatformViewCreated; + + const CustomPlatformView({ + this.creationParams, + this.onPlatformViewCreated, + this.scaleFactor, + this.filterQuality = FilterQuality.none, + Key? key, + }) : super(key: key); + + @override + _CustomPlatformViewState createState() => _CustomPlatformViewState(); +} + +class _CustomPlatformViewState extends State { + final GlobalKey _key = GlobalKey(); + final _downButtons = {}; + + PointerDeviceKind _pointerKind = PointerDeviceKind.unknown; + + MouseCursor _cursor = SystemMouseCursors.basic; + + final _controller = CustomPlatformViewController(); + final _focusNode = FocusNode(); + + StreamSubscription? _cursorSubscription; + + // Track click timing for double-click detection + DateTime? _lastClickTime; + Offset? _lastClickPosition; + int _clickCount = 0; + static const _doubleClickTimeout = Duration(milliseconds: 400); + static const _doubleClickDistance = 5.0; + + // Track active touch points for multi-touch support + final _activeTouchPoints = {}; + + @override + void initState() { + super.initState(); + + _controller.initialize( + onPlatformViewCreated: (id) { + widget.onPlatformViewCreated?.call(id); + setState(() {}); + }, + arguments: widget.creationParams, + ); + + // Report initial surface size + WidgetsBinding.instance.addPostFrameCallback((_) { + _reportSurfaceSize(); + }); + + _cursorSubscription = _controller._cursor.listen((cursor) { + setState(() { + _cursor = cursor; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Focus( + autofocus: true, + focusNode: _focusNode, + canRequestFocus: true, + debugLabel: "flutter_inappwebview_linux_custom_platform_view", + onFocusChange: (focused) { + // Notify the native webview when focus changes. + // This is important for proper text input handling - without this, + // clicking inside text fields requires double-click because the + // webview doesn't know it has focus. + if (_controller.value.isInitialized) { + _controller._setFocused(focused); + } + }, + onKeyEvent: _handleKeyEvent, + child: SizedBox.expand(key: _key, child: _buildInner()), + ); + } + + KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { + if (!_controller.value.isInitialized) { + return KeyEventResult.ignored; + } + + // Determine event type: 0=press, 1=release + int type; + if (event is KeyDownEvent) { + type = 0; + } else if (event is KeyUpEvent) { + type = 1; + } else if (event is KeyRepeatEvent) { + type = 2; // Repeat event + } else { + return KeyEventResult.ignored; + } + + // Build modifiers bitmask matching WPE's wpe_input_modifier enum: + // Control = bit 0, Shift = bit 1, Alt = bit 2, Meta = bit 3 + int modifiers = 0; + if (HardwareKeyboard.instance.isControlPressed) + modifiers |= 1; // wpe_input_keyboard_modifier_control + if (HardwareKeyboard.instance.isShiftPressed) + modifiers |= 2; // wpe_input_keyboard_modifier_shift + if (HardwareKeyboard.instance.isAltPressed) + modifiers |= 4; // wpe_input_keyboard_modifier_alt + if (HardwareKeyboard.instance.isMetaPressed) + modifiers |= 8; // wpe_input_keyboard_modifier_meta + + // For Ctrl/Meta combinations, don't send the character + final hasControlOrMeta = (modifiers & 0x9) != 0; // Ctrl=1 or Meta=8 + String? characters = hasControlOrMeta ? null : event.character; + + // Get X11 keysym for the key + final keyCode = getX11Keysym(event.logicalKey, event.character); + + // Get X11 keycode from physical key (USB HID -> evdev -> X11) + final usbHid = event.physicalKey.usbHidUsage & 0xFFFF; + final scanCode = usbHidToX11Keycode(usbHid); + + _controller._sendKeyEvent(type, keyCode, scanCode, modifiers, characters); + + return KeyEventResult.handled; + } + + Widget _buildInner() { + return NotificationListener( + onNotification: (notification) { + _reportSurfaceSize(); + return true; + }, + child: SizeChangedLayoutNotifier( + child: _controller.value.isInitialized + ? Listener( + behavior: HitTestBehavior.opaque, + onPointerHover: (ev) { + if (_pointerKind == PointerDeviceKind.touch) { + return; + } + _controller._setCursorPos(ev.localPosition); + }, + onPointerDown: (ev) async { + _reportSurfaceSize(); + + final needsFocus = !_focusNode.hasFocus; + if (needsFocus) { + // IMPORTANT: Set the native focus state BEFORE sending the click + // and AWAIT it to ensure WebKit has processed the focus change. + // Without awaiting, WebKit may receive the click before it has + // the focused activity state, causing the click to only activate + // the view rather than focusing the clicked element. + if (_controller.value.isInitialized) { + await _controller._setFocused(true); + } + _focusNode.requestFocus(); + } + + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + // Handle touch event + _activeTouchPoints[ev.pointer] = ev.localPosition; + _sendTouchEvent( + 0, + ev.pointer, + ev.localPosition, + ); // 0 = down + return; + } + + // Update cursor position first + _controller._setCursorPos(ev.localPosition); + + final button = _getButton(ev.buttons); + _downButtons[ev.pointer] = button; + + // Detect double/triple click + final now = DateTime.now(); + final timeSinceLastClick = _lastClickTime != null + ? now.difference(_lastClickTime!) + : const Duration(days: 1); + final distanceFromLastClick = _lastClickPosition != null + ? (ev.localPosition - _lastClickPosition!).distance + : double.infinity; + + if (timeSinceLastClick < _doubleClickTimeout && + distanceFromLastClick < _doubleClickDistance) { + _clickCount++; + if (_clickCount > 3) _clickCount = 1; + } else { + _clickCount = 1; + } + + _lastClickTime = now; + _lastClickPosition = ev.localPosition; + + // Send click with count for double/triple click + _controller._setPointerButtonStateWithClickCount( + InAppWebViewPointerEventKind.down, + button, + _clickCount, + ); + }, + onPointerUp: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + // Handle touch event + _activeTouchPoints[ev.pointer] = ev.localPosition; + _sendTouchEvent(1, ev.pointer, ev.localPosition); // 1 = up + _activeTouchPoints.remove(ev.pointer); + return; + } + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.up, + button, + ); + } + }, + onPointerCancel: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + // Handle touch cancel + _activeTouchPoints.remove(ev.pointer); + _sendTouchEvent( + 3, + ev.pointer, + ev.localPosition, + ); // 3 = cancel + return; + } + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.cancel, + button, + ); + } + }, + onPointerMove: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + // Handle touch move + _activeTouchPoints[ev.pointer] = ev.localPosition; + _sendTouchEvent( + 2, + ev.pointer, + ev.localPosition, + ); // 2 = move + return; + } + _controller._setCursorPos(ev.localPosition); + }, + onPointerSignal: (signal) { + if (signal is PointerScrollEvent) { + _controller._setScrollDelta( + -signal.scrollDelta.dx, + -signal.scrollDelta.dy, + ); + } + }, + onPointerPanZoomUpdate: (ev) { + _controller._setScrollDelta(ev.panDelta.dx, ev.panDelta.dy); + }, + child: MouseRegion( + cursor: _cursor, + onEnter: (ev) { + final button = _getButton(ev.buttons); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.enter, + button, + ); + }, + onExit: (ev) { + final button = _getButton(ev.buttons); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.leave, + button, + ); + }, + child: Texture( + textureId: _controller._textureId, + filterQuality: widget.filterQuality, + ), + ), + ) + : const SizedBox(), + ), + ); + } + + /// Sends a touch event to the webview with all active touch points + void _sendTouchEvent(int type, int pointerId, Offset position) { + // Build list of all active touch points for multi-touch support + final touchPoints = _activeTouchPoints.entries.map((entry) { + // Determine the type for each touch point in the event + // The main touch point uses the event type, others are motion + int pointType = entry.key == pointerId ? type : 2; // 2 = motion + return { + 'id': entry.key, + 'x': entry.value.dx, + 'y': entry.value.dy, + 'type': pointType, + }; + }).toList(); + + _controller._sendTouchEvent( + type, + pointerId, + position.dx, + position.dy, + touchPoints, + ); + } + + void _reportSurfaceSize() async { + final box = _key.currentContext?.findRenderObject() as RenderBox?; + if (box != null) { + await _controller.ready; + unawaited( + _controller._setSize( + box.size, + widget.scaleFactor ?? window.devicePixelRatio, + ), + ); + + // Also report the texture offset (position within the window) + final globalPosition = box.localToGlobal(Offset.zero); + unawaited(_controller._setTextureOffset(globalPosition)); + } + } + + @override + void dispose() { + super.dispose(); + _cursorSubscription?.cancel(); + _controller.dispose(); + _focusNode.dispose(); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/headless_in_app_webview.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/headless_in_app_webview.dart new file mode 100644 index 00000000..f6c057a6 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/headless_in_app_webview.dart @@ -0,0 +1,470 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../find_interaction/find_interaction_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'in_app_webview_controller.dart'; + +/// Object specifying creation parameters for creating a [LinuxHeadlessInAppWebView]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformHeadlessInAppWebViewCreationParams] for +/// more information. +@immutable +class LinuxHeadlessInAppWebViewCreationParams + extends PlatformHeadlessInAppWebViewCreationParams { + /// Creates a new [LinuxHeadlessInAppWebViewCreationParams] instance. + LinuxHeadlessInAppWebViewCreationParams({ + super.controllerFromPlatform, + super.initialSize, + super.windowId, + this.webViewEnvironment, + super.onWebViewCreated, + super.onLoadStart, + super.onLoadStop, + @Deprecated('Use onReceivedError instead') super.onLoadError, + super.onReceivedError, + @Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError, + super.onReceivedHttpError, + super.onProgressChanged, + super.onConsoleMessage, + super.shouldOverrideUrlLoading, + super.onLoadResource, + super.onScrollChanged, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + super.onLoadResourceCustomScheme, + super.onLoadResourceWithCustomScheme, + super.onCreateWindow, + super.onCloseWindow, + super.onJsAlert, + super.onJsConfirm, + super.onJsPrompt, + super.onReceivedHttpAuthRequest, + super.onReceivedServerTrustAuthRequest, + super.onReceivedClientCertRequest, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') + super.onFindResultReceived, + super.shouldInterceptAjaxRequest, + super.onAjaxReadyStateChange, + super.onAjaxProgress, + super.shouldInterceptFetchRequest, + super.onUpdateVisitedHistory, + @Deprecated("Use onPrintRequest instead") super.onPrint, + super.onPrintRequest, + super.onLongPressHitTestResult, + super.onEnterFullscreen, + super.onExitFullscreen, + super.onPageCommitVisible, + super.onTitleChanged, + super.onWindowFocus, + super.onWindowBlur, + super.onOverScrolled, + super.onZoomScaleChanged, + @Deprecated('Use onSafeBrowsingHit instead') super.androidOnSafeBrowsingHit, + super.onSafeBrowsingHit, + @Deprecated('Use onPermissionRequest instead') + super.androidOnPermissionRequest, + super.onPermissionRequest, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + super.androidOnGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsShowPrompt, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + super.androidOnGeolocationPermissionsHidePrompt, + super.onGeolocationPermissionsHidePrompt, + @Deprecated('Use shouldInterceptRequest instead') + super.androidShouldInterceptRequest, + super.shouldInterceptRequest, + @Deprecated('Use onRenderProcessGone instead') + super.androidOnRenderProcessGone, + super.onRenderProcessGone, + @Deprecated('Use onRenderProcessResponsive instead') + super.androidOnRenderProcessResponsive, + super.onRenderProcessResponsive, + @Deprecated('Use onRenderProcessUnresponsive instead') + super.androidOnRenderProcessUnresponsive, + super.onRenderProcessUnresponsive, + @Deprecated('Use onFormResubmission instead') + super.androidOnFormResubmission, + super.onFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') super.androidOnScaleChanged, + @Deprecated('Use onReceivedIcon instead') super.androidOnReceivedIcon, + super.onReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') + super.androidOnReceivedTouchIconUrl, + super.onReceivedTouchIconUrl, + @Deprecated('Use onJsBeforeUnload instead') super.androidOnJsBeforeUnload, + super.onJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') + super.androidOnReceivedLoginRequest, + super.onReceivedLoginRequest, + super.onPermissionRequestCanceled, + super.onRequestFocus, + @Deprecated('Use onWebContentProcessDidTerminate instead') + super.iosOnWebContentProcessDidTerminate, + super.onWebContentProcessDidTerminate, + @Deprecated( + 'Use onDidReceiveServerRedirectForProvisionalNavigation instead', + ) + super.iosOnDidReceiveServerRedirectForProvisionalNavigation, + super.onDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onNavigationResponse instead') + super.iosOnNavigationResponse, + super.onNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') + super.iosShouldAllowDeprecatedTLS, + super.shouldAllowDeprecatedTLS, + super.onCameraCaptureStateChanged, + super.onMicrophoneCaptureStateChanged, + super.onContentSizeChanged, + super.initialUrlRequest, + super.initialFile, + super.initialData, + @Deprecated('Use initialSettings instead') super.initialOptions, + super.initialSettings, + super.contextMenu, + super.initialUserScripts, + super.pullToRefreshController, + this.findInteractionController, + }); + + /// Creates a [LinuxHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams]. + LinuxHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( + PlatformHeadlessInAppWebViewCreationParams params, + ) : this( + controllerFromPlatform: params.controllerFromPlatform, + initialSize: params.initialSize, + windowId: params.windowId, + webViewEnvironment: + params.webViewEnvironment as LinuxWebViewEnvironment?, + onWebViewCreated: params.onWebViewCreated, + onLoadStart: params.onLoadStart, + onLoadStop: params.onLoadStop, + onLoadError: params.onLoadError, + onReceivedError: params.onReceivedError, + onLoadHttpError: params.onLoadHttpError, + onReceivedHttpError: params.onReceivedHttpError, + onProgressChanged: params.onProgressChanged, + onConsoleMessage: params.onConsoleMessage, + shouldOverrideUrlLoading: params.shouldOverrideUrlLoading, + onLoadResource: params.onLoadResource, + onScrollChanged: params.onScrollChanged, + onDownloadStart: params.onDownloadStart, + onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, + onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, + onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, + onCreateWindow: params.onCreateWindow, + onCloseWindow: params.onCloseWindow, + onJsAlert: params.onJsAlert, + onJsConfirm: params.onJsConfirm, + onJsPrompt: params.onJsPrompt, + onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest, + onReceivedServerTrustAuthRequest: + params.onReceivedServerTrustAuthRequest, + onReceivedClientCertRequest: params.onReceivedClientCertRequest, + onFindResultReceived: params.onFindResultReceived, + shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest, + onAjaxReadyStateChange: params.onAjaxReadyStateChange, + onAjaxProgress: params.onAjaxProgress, + shouldInterceptFetchRequest: params.shouldInterceptFetchRequest, + onUpdateVisitedHistory: params.onUpdateVisitedHistory, + onPrint: params.onPrint, + onPrintRequest: params.onPrintRequest, + onLongPressHitTestResult: params.onLongPressHitTestResult, + onEnterFullscreen: params.onEnterFullscreen, + onExitFullscreen: params.onExitFullscreen, + onPageCommitVisible: params.onPageCommitVisible, + onTitleChanged: params.onTitleChanged, + onWindowFocus: params.onWindowFocus, + onWindowBlur: params.onWindowBlur, + onOverScrolled: params.onOverScrolled, + onZoomScaleChanged: params.onZoomScaleChanged, + androidOnSafeBrowsingHit: params.androidOnSafeBrowsingHit, + onSafeBrowsingHit: params.onSafeBrowsingHit, + androidOnPermissionRequest: params.androidOnPermissionRequest, + onPermissionRequest: params.onPermissionRequest, + androidOnGeolocationPermissionsShowPrompt: + params.androidOnGeolocationPermissionsShowPrompt, + onGeolocationPermissionsShowPrompt: + params.onGeolocationPermissionsShowPrompt, + androidOnGeolocationPermissionsHidePrompt: + params.androidOnGeolocationPermissionsHidePrompt, + onGeolocationPermissionsHidePrompt: + params.onGeolocationPermissionsHidePrompt, + androidShouldInterceptRequest: params.androidShouldInterceptRequest, + shouldInterceptRequest: params.shouldInterceptRequest, + androidOnRenderProcessGone: params.androidOnRenderProcessGone, + onRenderProcessGone: params.onRenderProcessGone, + androidOnRenderProcessResponsive: + params.androidOnRenderProcessResponsive, + onRenderProcessResponsive: params.onRenderProcessResponsive, + androidOnRenderProcessUnresponsive: + params.androidOnRenderProcessUnresponsive, + onRenderProcessUnresponsive: params.onRenderProcessUnresponsive, + androidOnFormResubmission: params.androidOnFormResubmission, + onFormResubmission: params.onFormResubmission, + androidOnScaleChanged: params.androidOnScaleChanged, + androidOnReceivedIcon: params.androidOnReceivedIcon, + onReceivedIcon: params.onReceivedIcon, + androidOnReceivedTouchIconUrl: params.androidOnReceivedTouchIconUrl, + onReceivedTouchIconUrl: params.onReceivedTouchIconUrl, + androidOnJsBeforeUnload: params.androidOnJsBeforeUnload, + onJsBeforeUnload: params.onJsBeforeUnload, + androidOnReceivedLoginRequest: params.androidOnReceivedLoginRequest, + onReceivedLoginRequest: params.onReceivedLoginRequest, + onPermissionRequestCanceled: params.onPermissionRequestCanceled, + onRequestFocus: params.onRequestFocus, + iosOnWebContentProcessDidTerminate: + params.iosOnWebContentProcessDidTerminate, + onWebContentProcessDidTerminate: params.onWebContentProcessDidTerminate, + iosOnDidReceiveServerRedirectForProvisionalNavigation: + params.iosOnDidReceiveServerRedirectForProvisionalNavigation, + onDidReceiveServerRedirectForProvisionalNavigation: + params.onDidReceiveServerRedirectForProvisionalNavigation, + iosOnNavigationResponse: params.iosOnNavigationResponse, + onNavigationResponse: params.onNavigationResponse, + iosShouldAllowDeprecatedTLS: params.iosShouldAllowDeprecatedTLS, + shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS, + onCameraCaptureStateChanged: params.onCameraCaptureStateChanged, + onMicrophoneCaptureStateChanged: params.onMicrophoneCaptureStateChanged, + onContentSizeChanged: params.onContentSizeChanged, + initialUrlRequest: params.initialUrlRequest, + initialFile: params.initialFile, + initialData: params.initialData, + initialOptions: params.initialOptions, + initialSettings: params.initialSettings, + contextMenu: params.contextMenu, + initialUserScripts: params.initialUserScripts, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: + params.findInteractionController as LinuxFindInteractionController?, + ); + + @override + final LinuxWebViewEnvironment? webViewEnvironment; + + @override + final LinuxFindInteractionController? findInteractionController; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} +class LinuxHeadlessInAppWebView extends PlatformHeadlessInAppWebView + with ChannelController { + @override + late final String id; + + bool _started = false; + bool _running = false; + + static const MethodChannel _sharedChannel = const MethodChannel( + 'com.pichillilorenzo/flutter_headless_inappwebview', + ); + + LinuxInAppWebViewController? _webViewController; + + /// Constructs a [LinuxHeadlessInAppWebView]. + LinuxHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params) + : super.implementation( + params is LinuxHeadlessInAppWebViewCreationParams + ? params + : LinuxHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( + params, + ), + ) { + id = IdGenerator.generate(); + } + + static final LinuxHeadlessInAppWebView _staticValue = + LinuxHeadlessInAppWebView(LinuxHeadlessInAppWebViewCreationParams()); + + factory LinuxHeadlessInAppWebView.static() { + return _staticValue; + } + + @override + LinuxInAppWebViewController? get webViewController => _webViewController; + + dynamic _controllerFromPlatform; + + LinuxHeadlessInAppWebViewCreationParams get _linuxParams => + params as LinuxHeadlessInAppWebViewCreationParams; + + void _init() { + _webViewController = LinuxInAppWebViewController( + LinuxInAppWebViewControllerCreationParams(id: id, webviewParams: params), + ); + _controllerFromPlatform = + params.controllerFromPlatform?.call(_webViewController!) ?? + _webViewController!; + // Initialize the find interaction controller with the same ID + _linuxParams.findInteractionController?.init(id); + channel = MethodChannel( + 'com.pichillilorenzo/flutter_headless_inappwebview_$id', + ); + handler = _handleMethod; + initMethodCallHandler(); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onWebViewCreated": + if (params.onWebViewCreated != null && _webViewController != null) { + params.onWebViewCreated!(_controllerFromPlatform); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + Future run() async { + if (_started) { + return; + } + _started = true; + _init(); + + final initialSettings = params.initialSettings ?? InAppWebViewSettings(); + _inferInitialSettings(initialSettings); + + Map settingsMap = + (params.initialSettings != null ? initialSettings.toMap() : null) ?? + // ignore: deprecated_member_use_from_same_package + params.initialOptions?.toMap() ?? + initialSettings.toMap(); + + Map args = {}; + args.putIfAbsent('id', () => id); + args.putIfAbsent( + 'params', + () => { + 'initialUrlRequest': params.initialUrlRequest?.toMap(), + 'initialFile': params.initialFile, + 'initialData': params.initialData?.toMap(), + 'initialSettings': settingsMap, + 'contextMenu': params.contextMenu?.toMap() ?? {}, + 'windowId': params.windowId, + 'initialUserScripts': + params.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], + 'initialSize': params.initialSize.toMap(), + 'webViewEnvironmentId': params.webViewEnvironment?.id, + }, + ); + try { + await _sharedChannel.invokeMethod('run', args); + _running = true; + } catch (e) { + _running = false; + _started = false; + rethrow; + } + } + + void _inferInitialSettings(InAppWebViewSettings settings) { + if (params.shouldOverrideUrlLoading != null && + settings.useShouldOverrideUrlLoading == null) { + settings.useShouldOverrideUrlLoading = true; + } + if (params.onLoadResource != null && settings.useOnLoadResource == null) { + settings.useOnLoadResource = true; + } + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && + settings.useOnDownloadStart == null) { + settings.useOnDownloadStart = true; + } + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } + } + if (params.shouldInterceptFetchRequest != null && + settings.useShouldInterceptFetchRequest == null) { + settings.useShouldInterceptFetchRequest = true; + } + if (params.shouldInterceptRequest != null && + settings.useShouldInterceptRequest == null) { + settings.useShouldInterceptRequest = true; + } + if (params.onRenderProcessGone != null && + settings.useOnRenderProcessGone == null) { + settings.useOnRenderProcessGone = true; + } + if (params.onNavigationResponse != null && + settings.useOnNavigationResponse == null) { + settings.useOnNavigationResponse = true; + } + } + + @override + bool isRunning() { + return _running; + } + + @override + Future setSize(Size size) async { + if (!_running) { + return; + } + + Map args = {}; + args.putIfAbsent('size', () => size.toMap()); + await channel?.invokeMethod('setSize', args); + } + + @override + Future getSize() async { + if (!_running) { + return null; + } + + Map args = {}; + Map? sizeMap = (await channel?.invokeMethod( + 'getSize', + args, + ))?.cast(); + if (sizeMap == null) { + return null; + } + return MapSize.fromMap(sizeMap); + } + + @override + Future dispose() async { + if (!_running) { + return; + } + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + _started = false; + _running = false; + _webViewController?.dispose(); + _webViewController = null; + _controllerFromPlatform = null; + _linuxParams.findInteractionController?.dispose(); + } +} + +extension InternalHeadlessInAppWebView on LinuxHeadlessInAppWebView { + Future internalDispose() async { + _started = false; + _running = false; + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview.dart new file mode 100644 index 00000000..bd8bfa41 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview.dart @@ -0,0 +1,370 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../find_interaction/find_interaction_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'in_app_webview_controller.dart'; +import 'custom_platform_view.dart'; + +/// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +class LinuxInAppWebViewWidgetCreationParams + extends PlatformInAppWebViewWidgetCreationParams { + LinuxInAppWebViewWidgetCreationParams({ + super.controllerFromPlatform, + super.key, + super.layoutDirection, + super.gestureRecognizers, + super.headlessWebView, + super.keepAlive, + super.preventGestureDelay, + super.windowId, + this.webViewEnvironment, + super.onWebViewCreated, + super.onLoadStart, + super.onLoadStop, + @Deprecated('Use onReceivedError instead') super.onLoadError, + super.onReceivedError, + @Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError, + super.onReceivedHttpError, + super.onProgressChanged, + super.onConsoleMessage, + super.shouldOverrideUrlLoading, + super.onLoadResource, + super.onScrollChanged, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + super.onLoadResourceCustomScheme, + super.onLoadResourceWithCustomScheme, + super.onCreateWindow, + super.onCloseWindow, + super.onJsAlert, + super.onJsConfirm, + super.onJsPrompt, + super.onReceivedHttpAuthRequest, + super.onReceivedServerTrustAuthRequest, + super.onReceivedClientCertRequest, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') + super.onFindResultReceived, + super.shouldInterceptAjaxRequest, + super.onAjaxReadyStateChange, + super.onAjaxProgress, + super.shouldInterceptFetchRequest, + super.onUpdateVisitedHistory, + @Deprecated("Use onPrintRequest instead") super.onPrint, + super.onPrintRequest, + super.onLongPressHitTestResult, + super.onEnterFullscreen, + super.onExitFullscreen, + super.onPageCommitVisible, + super.onTitleChanged, + super.onWindowFocus, + super.onWindowBlur, + super.onOverScrolled, + super.onZoomScaleChanged, + super.onSafeBrowsingHit, + super.onPermissionRequest, + super.onGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsHidePrompt, + super.shouldInterceptRequest, + super.onRenderProcessGone, + super.onRenderProcessResponsive, + super.onRenderProcessUnresponsive, + super.onFormResubmission, + super.onReceivedIcon, + super.onReceivedTouchIconUrl, + super.onJsBeforeUnload, + super.onReceivedLoginRequest, + super.onPermissionRequestCanceled, + super.onRequestFocus, + super.onWebContentProcessDidTerminate, + super.onDidReceiveServerRedirectForProvisionalNavigation, + super.onNavigationResponse, + super.shouldAllowDeprecatedTLS, + super.onCameraCaptureStateChanged, + super.onMicrophoneCaptureStateChanged, + super.onContentSizeChanged, + super.initialUrlRequest, + super.initialFile, + super.initialData, + @Deprecated('Use initialSettings instead') super.initialOptions, + super.initialSettings, + super.contextMenu, + super.initialUserScripts, + super.pullToRefreshController, + super.findInteractionController, + }); + + /// Constructs a [LinuxInAppWebViewWidgetCreationParams] using a + /// [PlatformInAppWebViewWidgetCreationParams]. + LinuxInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams( + PlatformInAppWebViewWidgetCreationParams params, + ) : this( + controllerFromPlatform: params.controllerFromPlatform, + key: params.key, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + headlessWebView: params.headlessWebView, + keepAlive: params.keepAlive, + preventGestureDelay: params.preventGestureDelay, + windowId: params.windowId, + webViewEnvironment: + params.webViewEnvironment as LinuxWebViewEnvironment?, + onWebViewCreated: params.onWebViewCreated, + onLoadStart: params.onLoadStart, + onLoadStop: params.onLoadStop, + onLoadError: params.onLoadError, + onReceivedError: params.onReceivedError, + onLoadHttpError: params.onLoadHttpError, + onReceivedHttpError: params.onReceivedHttpError, + onProgressChanged: params.onProgressChanged, + onConsoleMessage: params.onConsoleMessage, + shouldOverrideUrlLoading: params.shouldOverrideUrlLoading, + onLoadResource: params.onLoadResource, + onScrollChanged: params.onScrollChanged, + onDownloadStart: params.onDownloadStart, + onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, + onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, + onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, + onCreateWindow: params.onCreateWindow, + onCloseWindow: params.onCloseWindow, + onJsAlert: params.onJsAlert, + onJsConfirm: params.onJsConfirm, + onJsPrompt: params.onJsPrompt, + onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest, + onReceivedServerTrustAuthRequest: + params.onReceivedServerTrustAuthRequest, + onReceivedClientCertRequest: params.onReceivedClientCertRequest, + onFindResultReceived: params.onFindResultReceived, + shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest, + onAjaxReadyStateChange: params.onAjaxReadyStateChange, + onAjaxProgress: params.onAjaxProgress, + shouldInterceptFetchRequest: params.shouldInterceptFetchRequest, + onUpdateVisitedHistory: params.onUpdateVisitedHistory, + onPrint: params.onPrint, + onPrintRequest: params.onPrintRequest, + onLongPressHitTestResult: params.onLongPressHitTestResult, + onEnterFullscreen: params.onEnterFullscreen, + onExitFullscreen: params.onExitFullscreen, + onPageCommitVisible: params.onPageCommitVisible, + onTitleChanged: params.onTitleChanged, + onWindowFocus: params.onWindowFocus, + onWindowBlur: params.onWindowBlur, + onOverScrolled: params.onOverScrolled, + onZoomScaleChanged: params.onZoomScaleChanged, + onSafeBrowsingHit: params.onSafeBrowsingHit, + onPermissionRequest: params.onPermissionRequest, + onGeolocationPermissionsShowPrompt: + params.onGeolocationPermissionsShowPrompt, + onGeolocationPermissionsHidePrompt: + params.onGeolocationPermissionsHidePrompt, + shouldInterceptRequest: params.shouldInterceptRequest, + onRenderProcessGone: params.onRenderProcessGone, + onRenderProcessResponsive: params.onRenderProcessResponsive, + onRenderProcessUnresponsive: params.onRenderProcessUnresponsive, + onFormResubmission: params.onFormResubmission, + onReceivedIcon: params.onReceivedIcon, + onReceivedTouchIconUrl: params.onReceivedTouchIconUrl, + onJsBeforeUnload: params.onJsBeforeUnload, + onReceivedLoginRequest: params.onReceivedLoginRequest, + onPermissionRequestCanceled: params.onPermissionRequestCanceled, + onRequestFocus: params.onRequestFocus, + onWebContentProcessDidTerminate: params.onWebContentProcessDidTerminate, + onDidReceiveServerRedirectForProvisionalNavigation: + params.onDidReceiveServerRedirectForProvisionalNavigation, + onNavigationResponse: params.onNavigationResponse, + shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS, + onCameraCaptureStateChanged: params.onCameraCaptureStateChanged, + onMicrophoneCaptureStateChanged: params.onMicrophoneCaptureStateChanged, + onContentSizeChanged: params.onContentSizeChanged, + initialUrlRequest: params.initialUrlRequest, + initialFile: params.initialFile, + initialData: params.initialData, + initialOptions: params.initialOptions, + initialSettings: params.initialSettings, + contextMenu: params.contextMenu, + initialUserScripts: params.initialUserScripts, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: params.findInteractionController, + ); + + @override + final LinuxWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} +class LinuxInAppWebViewWidget extends PlatformInAppWebViewWidget { + /// Constructs a [LinuxInAppWebViewWidget]. + /// + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} + LinuxInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params) + : super.implementation( + params is LinuxInAppWebViewWidgetCreationParams + ? params + : LinuxInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams( + params, + ), + ); + + LinuxInAppWebViewWidgetCreationParams get _linuxParams => + params as LinuxInAppWebViewWidgetCreationParams; + + LinuxInAppWebViewController? _controller; + + static final LinuxInAppWebViewWidget _staticValue = LinuxInAppWebViewWidget( + LinuxInAppWebViewWidgetCreationParams(), + ); + + factory LinuxInAppWebViewWidget.static() { + return _staticValue; + } + + @override + Widget build(BuildContext context) { + final initialSettings = params.initialSettings ?? InAppWebViewSettings(); + _inferInitialSettings(initialSettings); + + Map settingsMap = + (params.initialSettings != null ? initialSettings.toMap() : null) ?? + // ignore: deprecated_member_use_from_same_package + params.initialOptions?.toMap() ?? + initialSettings.toMap(); + + if ((params.headlessWebView?.isRunning() ?? false) && + params.keepAlive != null) { + final headlessId = params.headlessWebView?.id; + if (headlessId != null) { + // force keep alive id to match headless webview id + params.keepAlive?.id = headlessId; + } + } + + return CustomPlatformView( + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: { + 'initialUrlRequest': params.initialUrlRequest?.toMap(), + 'initialFile': params.initialFile, + 'initialData': params.initialData?.toMap(), + 'initialSettings': settingsMap, + 'contextMenu': params.contextMenu?.toMap() ?? {}, + 'windowId': params.windowId, + 'headlessWebViewId': params.headlessWebView?.isRunning() ?? false + ? params.headlessWebView?.id + : null, + 'initialUserScripts': + params.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], + 'keepAliveId': params.keepAlive?.id, + 'webViewEnvironmentId': params.webViewEnvironment?.id, + }, + ); + } + + void _onPlatformViewCreated(int id) { + dynamic viewId = id; + if (params.headlessWebView?.isRunning() ?? false) { + viewId = params.headlessWebView?.id; + } + viewId = params.keepAlive?.id ?? viewId ?? id; + + _controller = LinuxInAppWebViewController( + PlatformInAppWebViewControllerCreationParams( + id: viewId, + webviewParams: params, + ), + ); + + // Initialize the find interaction controller with the same view ID + if (_linuxParams.findInteractionController != null) { + var findInteractionController = + _linuxParams.findInteractionController + as LinuxFindInteractionController; + findInteractionController.init(viewId); + } + + debugLog( + className: runtimeType.toString(), + id: viewId?.toString(), + debugLoggingSettings: PlatformInAppWebViewController.debugLoggingSettings, + method: "onWebViewCreated", + args: [], + ); + if (params.onWebViewCreated != null) { + params.onWebViewCreated!( + params.controllerFromPlatform?.call(_controller!) ?? _controller!, + ); + } + } + + void _inferInitialSettings(InAppWebViewSettings settings) { + if (params.shouldOverrideUrlLoading != null && + settings.useShouldOverrideUrlLoading == null) { + settings.useShouldOverrideUrlLoading = true; + } + if (params.onLoadResource != null && settings.useOnLoadResource == null) { + settings.useOnLoadResource = true; + } + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && + settings.useOnDownloadStart == null) { + settings.useOnDownloadStart = true; + } + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } + } + if (params.shouldInterceptFetchRequest != null && + settings.useShouldInterceptFetchRequest == null) { + settings.useShouldInterceptFetchRequest = true; + } + if (params.shouldInterceptRequest != null && + settings.useShouldInterceptRequest == null) { + settings.useShouldInterceptRequest = true; + } + if (params.onRenderProcessGone != null && + settings.useOnRenderProcessGone == null) { + settings.useOnRenderProcessGone = true; + } + if (params.onNavigationResponse != null && + settings.useOnNavigationResponse == null) { + settings.useOnNavigationResponse = true; + } + } + + @override + void dispose() { + dynamic viewId = _controller?.id; + debugLog( + className: runtimeType.toString(), + id: viewId?.toString(), + debugLoggingSettings: PlatformInAppWebViewController.debugLoggingSettings, + method: "dispose", + args: [], + ); + final isKeepAlive = params.keepAlive != null; + _controller?.dispose(isKeepAlive: isKeepAlive); + _controller = null; + params.findInteractionController?.dispose(isKeepAlive: isKeepAlive); + } + + @override + T controllerFromPlatform(PlatformInAppWebViewController controller) { + // unused + throw UnimplementedError(); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview_controller.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview_controller.dart new file mode 100644 index 00000000..b4f54088 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/in_app_webview_controller.dart @@ -0,0 +1,2362 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:core'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:developer' as developer; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../in_app_browser/in_app_browser.dart'; +import '../web_message/web_message_channel.dart'; +import '../web_message/web_message_listener.dart'; +import '../web_storage/web_storage.dart'; +import '_static_channel.dart'; + +/// Object specifying creation parameters for creating a [LinuxInAppWebViewController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformInAppWebViewControllerCreationParams] for +/// more information. +@immutable +class LinuxInAppWebViewControllerCreationParams + extends PlatformInAppWebViewControllerCreationParams { + /// Creates a new [LinuxInAppWebViewControllerCreationParams] instance. + const LinuxInAppWebViewControllerCreationParams({ + required super.id, + super.webviewParams, + }); + + /// Creates a [LinuxInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams]. + factory LinuxInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams( + PlatformInAppWebViewControllerCreationParams params, + ) { + return LinuxInAppWebViewControllerCreationParams( + id: params.id, + webviewParams: params.webviewParams, + ); + } +} + +/// Controls a WebView, such as an [InAppWebView] widget instance. +/// +/// If you are using the [InAppWebView] widget, an [InAppWebViewController] instance +/// can be obtained by setting the [InAppWebView.onWebViewCreated] callback. +class LinuxInAppWebViewController extends PlatformInAppWebViewController + with ChannelController { + static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; + + // List of properties to be saved and restored for keep alive feature + Map _javaScriptHandlersMap = HashMap(); + Map> _userScripts = { + UserScriptInjectionTime.AT_DOCUMENT_START: [], + UserScriptInjectionTime.AT_DOCUMENT_END: [], + }; + Set _webMessageListeners = Set(); + Set _webMessageListenerObjNames = Set(); + Set _webMessageChannels = Set(); + Map _injectedScriptsFromURL = {}; + + // static map that contains the properties to be saved and restored for keep alive feature + static final Map + _keepAliveMap = {}; + + LinuxInAppBrowser? _inAppBrowser; + + PlatformInAppBrowserEvents? get _inAppBrowserEventHandler => + _inAppBrowser?.eventHandler; + + dynamic _controllerFromPlatform; + + @override + late LinuxWebStorage webStorage; + + LinuxInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params, + ) : super.implementation( + params is LinuxInAppWebViewControllerCreationParams + ? params + : LinuxInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams( + params, + ), + ) { + channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); + handler = _handleMethod; + initMethodCallHandler(); + + final initialUserScripts = webviewParams?.initialUserScripts; + if (initialUserScripts != null) { + for (final userScript in initialUserScripts) { + if (userScript.injectionTime == + UserScriptInjectionTime.AT_DOCUMENT_START) { + this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.add( + userScript, + ); + } else { + this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.add( + userScript, + ); + } + } + } + + this._init(params); + } + + static final LinuxInAppWebViewController _staticValue = + LinuxInAppWebViewController( + LinuxInAppWebViewControllerCreationParams(id: null), + ); + + factory LinuxInAppWebViewController.static() { + return _staticValue; + } + + LinuxInAppWebViewController.fromInAppBrowser( + PlatformInAppWebViewControllerCreationParams params, + MethodChannel channel, + LinuxInAppBrowser inAppBrowser, + UnmodifiableListView? initialUserScripts, + ) : super.implementation( + params is LinuxInAppWebViewControllerCreationParams + ? params + : LinuxInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams( + params, + ), + ) { + this.channel = channel; + this._inAppBrowser = inAppBrowser; + + if (initialUserScripts != null) { + for (final userScript in initialUserScripts) { + if (userScript.injectionTime == + UserScriptInjectionTime.AT_DOCUMENT_START) { + this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.add( + userScript, + ); + } else { + this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.add( + userScript, + ); + } + } + } + this._init(params); + } + + void _init(PlatformInAppWebViewControllerCreationParams params) { + _controllerFromPlatform = + params.webviewParams?.controllerFromPlatform?.call(this) ?? this; + + webStorage = LinuxWebStorage( + LinuxWebStorageCreationParams( + localStorage: LinuxLocalStorage.defaultStorage(controller: this), + sessionStorage: LinuxSessionStorage.defaultStorage(controller: this), + ), + ); + + if (params.webviewParams is PlatformInAppWebViewWidgetCreationParams) { + final keepAlive = + (params.webviewParams as PlatformInAppWebViewWidgetCreationParams) + .keepAlive; + if (keepAlive != null) { + InAppWebViewControllerKeepAliveProps? props = _keepAliveMap[keepAlive]; + if (props == null) { + // save controller properties to restore it later + _keepAliveMap[keepAlive] = InAppWebViewControllerKeepAliveProps( + injectedScriptsFromURL: _injectedScriptsFromURL, + javaScriptHandlersMap: _javaScriptHandlersMap, + userScripts: _userScripts, + webMessageListenerObjNames: _webMessageListenerObjNames, + ); + } else { + // restore controller properties + _injectedScriptsFromURL = props.injectedScriptsFromURL; + _javaScriptHandlersMap = props.javaScriptHandlersMap; + _userScripts = props.userScripts; + _webMessageListenerObjNames = props.webMessageListenerObjNames; + } + } + } + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + name: _inAppBrowser == null + ? "WebView" + : _inAppBrowser.runtimeType.toString(), + id: (getViewId() ?? _inAppBrowser?.id).toString(), + debugLoggingSettings: PlatformInAppWebViewController.debugLoggingSettings, + method: method, + args: args, + ); + } + + Future _handleMethod(MethodCall call) async { + if (PlatformInAppWebViewController.debugLoggingSettings.enabled && + call.method != "onCallJsHandler") { + _debugLog(call.method, call.arguments); + } + + switch (call.method) { + case "onLoadStart": + _injectedScriptsFromURL.clear(); + if ((webviewParams != null && webviewParams!.onLoadStart != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && webviewParams!.onLoadStart != null) + webviewParams!.onLoadStart!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onLoadStart(uri); + } + break; + case "onLoadStop": + if ((webviewParams != null && webviewParams!.onLoadStop != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && webviewParams!.onLoadStop != null) + webviewParams!.onLoadStop!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onLoadStop(uri); + } + break; + case "shouldOverrideUrlLoading": + if ((webviewParams != null && + webviewParams!.shouldOverrideUrlLoading != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + NavigationAction navigationAction = NavigationAction.fromMap( + arguments, + )!; + + if (webviewParams != null && + webviewParams!.shouldOverrideUrlLoading != null) + return (await webviewParams!.shouldOverrideUrlLoading!( + _controllerFromPlatform, + navigationAction, + ))?.toNativeValue(); + return (await _inAppBrowserEventHandler!.shouldOverrideUrlLoading( + navigationAction, + ))?.toNativeValue(); + } + break; + case "onProgressChanged": + if ((webviewParams != null && + webviewParams!.onProgressChanged != null) || + _inAppBrowserEventHandler != null) { + int progress = call.arguments["progress"]; + if (webviewParams != null && webviewParams!.onProgressChanged != null) + webviewParams!.onProgressChanged!( + _controllerFromPlatform, + progress, + ); + else + _inAppBrowserEventHandler!.onProgressChanged(progress); + } + break; + case "onConsoleMessage": + if ((webviewParams != null && + webviewParams!.onConsoleMessage != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + ConsoleMessage consoleMessage = ConsoleMessage.fromMap(arguments)!; + if (webviewParams != null && webviewParams!.onConsoleMessage != null) + webviewParams!.onConsoleMessage!( + _controllerFromPlatform, + consoleMessage, + ); + else + _inAppBrowserEventHandler!.onConsoleMessage(consoleMessage); + } + break; + case "onLoadResource": + if ((webviewParams != null && webviewParams!.onLoadResource != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + LoadedResource resource = LoadedResource.fromMap(arguments)!; + if (webviewParams != null && webviewParams!.onLoadResource != null) + webviewParams!.onLoadResource!(_controllerFromPlatform, resource); + else + _inAppBrowserEventHandler!.onLoadResource(resource); + } + break; + case "onTitleChanged": + if ((webviewParams != null && webviewParams!.onTitleChanged != null) || + _inAppBrowserEventHandler != null) { + String? title = call.arguments["title"]; + if (webviewParams != null && webviewParams!.onTitleChanged != null) + webviewParams!.onTitleChanged!(_controllerFromPlatform, title); + else + _inAppBrowserEventHandler!.onTitleChanged(title); + } + break; + case "onUpdateVisitedHistory": + if ((webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + bool? isReload = call.arguments["isReload"]; + if (webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) + webviewParams!.onUpdateVisitedHistory!( + _controllerFromPlatform, + uri, + isReload, + ); + else + _inAppBrowserEventHandler!.onUpdateVisitedHistory(uri, isReload); + } + break; + case "onReceivedError": + if ((webviewParams != null && + (webviewParams!.onReceivedError != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadError != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + WebResourceRequest request = WebResourceRequest.fromMap( + arguments["request"]?.cast(), + )!; + WebResourceError error = WebResourceError.fromMap( + arguments["error"]?.cast(), + )!; + var isForMainFrame = request.isForMainFrame ?? false; + + if (webviewParams != null) { + if (webviewParams!.onReceivedError != null) + webviewParams!.onReceivedError!( + _controllerFromPlatform, + request, + error, + ); + else if (isForMainFrame) { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadError!( + _controllerFromPlatform, + request.url, + error.type.toNativeValue() ?? -1, + error.description, + ); + } + } else { + if (isForMainFrame) { + _inAppBrowserEventHandler!.onLoadError( + request.url, + error.type.toNativeValue() ?? -1, + error.description, + ); + } + _inAppBrowserEventHandler!.onReceivedError(request, error); + } + } + break; + case "onReceivedHttpError": + if ((webviewParams != null && + (webviewParams!.onReceivedHttpError != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadHttpError != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + WebResourceRequest request = WebResourceRequest.fromMap( + arguments["request"]?.cast(), + )!; + WebResourceResponse errorResponse = WebResourceResponse.fromMap( + arguments["errorResponse"]?.cast(), + )!; + var isForMainFrame = request.isForMainFrame ?? false; + + if (webviewParams != null) { + if (webviewParams!.onReceivedHttpError != null) + webviewParams!.onReceivedHttpError!( + _controllerFromPlatform, + request, + errorResponse, + ); + else if (isForMainFrame) { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadHttpError!( + _controllerFromPlatform, + request.url, + errorResponse.statusCode ?? -1, + errorResponse.reasonPhrase ?? '', + ); + } + } else { + if (isForMainFrame) { + _inAppBrowserEventHandler!.onLoadHttpError( + request.url, + errorResponse.statusCode ?? -1, + errorResponse.reasonPhrase ?? '', + ); + } + _inAppBrowserEventHandler!.onReceivedHttpError( + request, + errorResponse, + ); + } + } + break; + case "onPageCommitVisible": + if ((webviewParams != null && + webviewParams!.onPageCommitVisible != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && + webviewParams!.onPageCommitVisible != null) + webviewParams!.onPageCommitVisible!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onPageCommitVisible(uri); + } + break; + case "onZoomScaleChanged": + if ((webviewParams != null && + webviewParams!.onZoomScaleChanged != null) || + _inAppBrowserEventHandler != null) { + double oldScale = call.arguments["oldScale"]; + double newScale = call.arguments["newScale"]; + if (webviewParams != null && + webviewParams!.onZoomScaleChanged != null) + webviewParams!.onZoomScaleChanged!( + _controllerFromPlatform, + oldScale, + newScale, + ); + else + _inAppBrowserEventHandler!.onZoomScaleChanged(oldScale, newScale); + } + break; + case "onScrollChanged": + if ((webviewParams != null && webviewParams!.onScrollChanged != null) || + _inAppBrowserEventHandler != null) { + int x = call.arguments["x"]; + int y = call.arguments["y"]; + if (webviewParams != null && webviewParams!.onScrollChanged != null) + webviewParams!.onScrollChanged!(_controllerFromPlatform, x, y); + else + _inAppBrowserEventHandler!.onScrollChanged(x, y); + } + break; + case "onCloseWindow": + if (webviewParams != null && webviewParams!.onCloseWindow != null) + webviewParams!.onCloseWindow!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onCloseWindow(); + break; + case "onCreateWindow": + if ((webviewParams != null && webviewParams!.onCreateWindow != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + CreateWindowAction createWindowAction = CreateWindowAction.fromMap( + arguments, + )!; + + if (webviewParams != null && webviewParams!.onCreateWindow != null) + return await webviewParams!.onCreateWindow!( + _controllerFromPlatform, + createWindowAction, + ); + else + return await _inAppBrowserEventHandler!.onCreateWindow( + createWindowAction, + ); + } + return false; + case "onJsAlert": + if ((webviewParams != null && webviewParams!.onJsAlert != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + JsAlertRequest jsAlertRequest = JsAlertRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsAlert != null) + return (await webviewParams!.onJsAlert!( + _controllerFromPlatform, + jsAlertRequest, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onJsAlert( + jsAlertRequest, + ))?.toMap(); + } + return null; + case "onJsConfirm": + if ((webviewParams != null && webviewParams!.onJsConfirm != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + JsConfirmRequest jsConfirmRequest = JsConfirmRequest.fromMap( + arguments, + )!; + + if (webviewParams != null && webviewParams!.onJsConfirm != null) + return (await webviewParams!.onJsConfirm!( + _controllerFromPlatform, + jsConfirmRequest, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onJsConfirm( + jsConfirmRequest, + ))?.toMap(); + } + return null; + case "onJsPrompt": + if ((webviewParams != null && webviewParams!.onJsPrompt != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + JsPromptRequest jsPromptRequest = JsPromptRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsPrompt != null) + return (await webviewParams!.onJsPrompt!( + _controllerFromPlatform, + jsPromptRequest, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onJsPrompt( + jsPromptRequest, + ))?.toMap(); + } + return null; + case "onJsBeforeUnload": + if ((webviewParams != null && + webviewParams!.onJsBeforeUnload != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + JsBeforeUnloadRequest jsBeforeUnloadRequest = + JsBeforeUnloadRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsBeforeUnload != null) + return (await webviewParams!.onJsBeforeUnload!( + _controllerFromPlatform, + jsBeforeUnloadRequest, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onJsBeforeUnload( + jsBeforeUnloadRequest, + ))?.toMap(); + } + return null; + case "onPermissionRequest": + if ((webviewParams != null && + webviewParams!.onPermissionRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + PermissionRequest permissionRequest = PermissionRequest.fromMap( + arguments, + )!; + + if (webviewParams != null && + webviewParams!.onPermissionRequest != null) + return (await webviewParams!.onPermissionRequest!( + _controllerFromPlatform, + permissionRequest, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onPermissionRequest( + permissionRequest, + ))?.toMap(); + } + return null; + case "onReceivedHttpAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedHttpAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + HttpAuthenticationChallenge challenge = + HttpAuthenticationChallenge.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onReceivedHttpAuthRequest != null) + return (await webviewParams!.onReceivedHttpAuthRequest!( + _controllerFromPlatform, + challenge, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onReceivedHttpAuthRequest( + challenge, + ))?.toMap(); + } + return null; + case "onReceivedServerTrustAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedServerTrustAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + ServerTrustChallenge challenge = ServerTrustChallenge.fromMap( + arguments, + )!; + + if (webviewParams != null && + webviewParams!.onReceivedServerTrustAuthRequest != null) + return (await webviewParams!.onReceivedServerTrustAuthRequest!( + _controllerFromPlatform, + challenge, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onReceivedServerTrustAuthRequest(challenge)) + ?.toMap(); + } + return null; + case "onReceivedClientCertRequest": + if ((webviewParams != null && + webviewParams!.onReceivedClientCertRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + ClientCertChallenge challenge = ClientCertChallenge.fromMap( + arguments, + )!; + + if (webviewParams != null && + webviewParams!.onReceivedClientCertRequest != null) + return (await webviewParams!.onReceivedClientCertRequest!( + _controllerFromPlatform, + challenge, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onReceivedClientCertRequest(challenge)) + ?.toMap(); + } + return null; + case "onDownloadStarting": + if ((webviewParams != null && + (webviewParams!.onDownloadStart != null || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + DownloadStartRequest downloadStartRequest = + DownloadStartRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, + downloadStartRequest, + ))?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) + webviewParams!.onDownloadStartRequest!( + _controllerFromPlatform, + downloadStartRequest, + ); + else { + webviewParams!.onDownloadStart!( + _controllerFromPlatform, + downloadStartRequest.url, + ); + } + } else { + _inAppBrowserEventHandler!.onDownloadStart( + downloadStartRequest.url, + ); + _inAppBrowserEventHandler!.onDownloadStartRequest( + downloadStartRequest, + ); + return (await _inAppBrowserEventHandler!.onDownloadStarting( + downloadStartRequest, + ))?.toMap(); + } + } + return null; + case "onEnterFullscreen": + if (webviewParams != null && webviewParams!.onEnterFullscreen != null) + webviewParams!.onEnterFullscreen!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onEnterFullscreen(); + break; + case "onExitFullscreen": + if (webviewParams != null && webviewParams!.onExitFullscreen != null) + webviewParams!.onExitFullscreen!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onExitFullscreen(); + break; + case "onReceivedIcon": + if ((webviewParams != null && webviewParams!.onReceivedIcon != null) || + _inAppBrowserEventHandler != null) { + // For now, we just have the URL, not the actual icon data + // This could be enhanced to download the favicon + String? faviconUrl = call.arguments["url"]; + if (faviconUrl != null) { + // Create a placeholder Uint8List since we don't have the actual icon + // The favicon URL could be used to download the icon if needed + if (webviewParams != null && webviewParams!.onReceivedIcon != null) + webviewParams!.onReceivedIcon!( + _controllerFromPlatform, + Uint8List(0), + ); + else + _inAppBrowserEventHandler!.onReceivedIcon(Uint8List(0)); + } + } + break; + case "onCreateContextMenu": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null && contextMenu.onCreateContextMenu != null) { + Map arguments = call.arguments + .cast(); + InAppWebViewHitTestResult hitTestResult = + InAppWebViewHitTestResult.fromMap(arguments)!; + contextMenu.onCreateContextMenu!(hitTestResult); + } + break; + case "onHideContextMenu": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null && contextMenu.onHideContextMenu != null) { + contextMenu.onHideContextMenu!(); + } + break; + case "onContextMenuActionItemClicked": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null) { + Map arguments = call.arguments + .cast(); + String id = arguments["id"] ?? ""; + String title = arguments["title"] ?? ""; + + ContextMenuItem menuItemClicked = ContextMenuItem( + id: id, + title: title, + action: null, + ); + + // Check if this matches any custom menu items + for (var menuItem in contextMenu.menuItems) { + if (menuItem.id == id) { + menuItemClicked = menuItem; + if (menuItem.action != null) { + menuItem.action!(); + } + break; + } + } + + if (contextMenu.onContextMenuActionItemClicked != null) { + contextMenu.onContextMenuActionItemClicked!(menuItemClicked); + } + } + break; + + case "onRenderProcessGone": + if ((webviewParams != null && + webviewParams!.onRenderProcessGone != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + RenderProcessGoneDetail detail = RenderProcessGoneDetail.fromMap( + arguments, + )!; + if (webviewParams != null && + webviewParams!.onRenderProcessGone != null) + webviewParams!.onRenderProcessGone!( + _controllerFromPlatform, + detail, + ); + else + _inAppBrowserEventHandler!.onRenderProcessGone(detail); + } + break; + case "onNavigationResponse": + if ((webviewParams != null && + webviewParams!.onNavigationResponse != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + NavigationResponse navigationResponse = NavigationResponse.fromMap( + arguments, + )!; + + if (webviewParams != null && + webviewParams!.onNavigationResponse != null) + return (await webviewParams!.onNavigationResponse!( + _controllerFromPlatform, + navigationResponse, + ))?.toNativeValue(); + else + return (await _inAppBrowserEventHandler!.onNavigationResponse( + navigationResponse, + ))?.toNativeValue(); + } + break; + case "onPrintRequest": + if ((webviewParams != null && + (webviewParams!.onPrintRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onPrint != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onPrintRequest != null) + return await webviewParams!.onPrintRequest!( + _controllerFromPlatform, + uri, + null, // PrintJobController not supported on Linux + ); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onPrint!(_controllerFromPlatform, uri); + return false; + } + } else { + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.onPrint(uri); + return await _inAppBrowserEventHandler!.onPrintRequest( + uri, + null, // PrintJobController not supported on Linux + ); + } + } + break; + case "onCameraCaptureStateChanged": + if ((webviewParams != null && + webviewParams!.onCameraCaptureStateChanged != null) || + _inAppBrowserEventHandler != null) { + var oldState = MediaCaptureState.fromNativeValue( + call.arguments["oldState"], + ); + var newState = MediaCaptureState.fromNativeValue( + call.arguments["newState"], + ); + + if (webviewParams != null && + webviewParams!.onCameraCaptureStateChanged != null) + webviewParams!.onCameraCaptureStateChanged!( + _controllerFromPlatform, + oldState, + newState, + ); + else + _inAppBrowserEventHandler!.onCameraCaptureStateChanged( + oldState, + newState, + ); + } + break; + case "onMicrophoneCaptureStateChanged": + if ((webviewParams != null && + webviewParams!.onMicrophoneCaptureStateChanged != null) || + _inAppBrowserEventHandler != null) { + var oldState = MediaCaptureState.fromNativeValue( + call.arguments["oldState"], + ); + var newState = MediaCaptureState.fromNativeValue( + call.arguments["newState"], + ); + + if (webviewParams != null && + webviewParams!.onMicrophoneCaptureStateChanged != null) + webviewParams!.onMicrophoneCaptureStateChanged!( + _controllerFromPlatform, + oldState, + newState, + ); + else + _inAppBrowserEventHandler!.onMicrophoneCaptureStateChanged( + oldState, + newState, + ); + } + break; + case "onShowFileChooser": + if ((webviewParams != null && + webviewParams!.onShowFileChooser != null) || + _inAppBrowserEventHandler != null) { + Map arguments = call.arguments + .cast(); + ShowFileChooserRequest request = ShowFileChooserRequest.fromMap( + arguments, + )!; + + if (webviewParams != null && webviewParams!.onShowFileChooser != null) + return (await webviewParams!.onShowFileChooser!( + _controllerFromPlatform, + request, + ))?.toMap(); + else + return (await _inAppBrowserEventHandler!.onShowFileChooser( + request, + ))?.toMap(); + } + return null; + // onFindResultReceived is now handled by FindInteractionController + case "onLoadResourceWithCustomScheme": + if ((webviewParams != null && + (webviewParams!.onLoadResourceWithCustomScheme != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadResourceCustomScheme != null)) || + _inAppBrowserEventHandler != null) { + Map requestMap = call.arguments + .cast(); + WebResourceRequest request = WebResourceRequest.fromMap(requestMap)!; + + if (webviewParams != null) { + if (webviewParams!.onLoadResourceWithCustomScheme != null) + return (await webviewParams!.onLoadResourceWithCustomScheme!( + _controllerFromPlatform, + request, + ))?.toMap(); + else { + return (await webviewParams! + // ignore: deprecated_member_use_from_same_package + .onLoadResourceCustomScheme!( + _controllerFromPlatform, + request.url, + )) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onLoadResourceWithCustomScheme(request)) ?? + (await _inAppBrowserEventHandler! + .onLoadResourceCustomScheme(request.url))) + ?.toMap(); + } + } + break; + case "onCallJsHandler": + String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = call.arguments["data"] + .cast(); + // decode args to json + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = JavaScriptHandlerFunctionData.fromMap( + handlerDataMap, + )!; + + _debugLog(handlerName, handlerData); + + switch (handlerName) { + case "onLoadResource": + if ((webviewParams != null && + webviewParams!.onLoadResource != null) || + _inAppBrowserEventHandler != null) { + Map arguments = handlerData.args[0] + .cast(); + arguments["startTime"] = arguments["startTime"] is int + ? arguments["startTime"].toDouble() + : arguments["startTime"]; + arguments["duration"] = arguments["duration"] is int + ? arguments["duration"].toDouble() + : arguments["duration"]; + + var response = LoadedResource.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onLoadResource != null) + webviewParams!.onLoadResource!( + _controllerFromPlatform, + response, + ); + else + _inAppBrowserEventHandler!.onLoadResource(response); + } + return null; + case "shouldInterceptAjaxRequest": + if ((webviewParams != null && + webviewParams!.shouldInterceptAjaxRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = handlerData.args[0] + .cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.shouldInterceptAjaxRequest != null) + return jsonEncode( + await webviewParams!.shouldInterceptAjaxRequest!( + _controllerFromPlatform, + request, + ), + ); + else + return jsonEncode( + await _inAppBrowserEventHandler!.shouldInterceptAjaxRequest( + request, + ), + ); + } + return null; + case "onAjaxReadyStateChange": + if ((webviewParams != null && + webviewParams!.onAjaxReadyStateChange != null) || + _inAppBrowserEventHandler != null) { + Map arguments = handlerData.args[0] + .cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAjaxReadyStateChange != null) + return jsonEncode( + (await webviewParams!.onAjaxReadyStateChange!( + _controllerFromPlatform, + request, + ))?.toNativeValue(), + ); + else + return jsonEncode( + (await _inAppBrowserEventHandler!.onAjaxReadyStateChange( + request, + ))?.toNativeValue(), + ); + } + return null; + case "onAjaxProgress": + if ((webviewParams != null && + webviewParams!.onAjaxProgress != null) || + _inAppBrowserEventHandler != null) { + Map arguments = handlerData.args[0] + .cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAjaxProgress != null) + return jsonEncode( + (await webviewParams!.onAjaxProgress!( + _controllerFromPlatform, + request, + ))?.toNativeValue(), + ); + else + return jsonEncode( + (await _inAppBrowserEventHandler!.onAjaxProgress( + request, + ))?.toNativeValue(), + ); + } + return null; + case "shouldInterceptFetchRequest": + if ((webviewParams != null && + webviewParams!.shouldInterceptFetchRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = handlerData.args[0] + .cast(); + FetchRequest request = FetchRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.shouldInterceptFetchRequest != null) + return jsonEncode( + await webviewParams!.shouldInterceptFetchRequest!( + _controllerFromPlatform, + request, + ), + ); + else + return jsonEncode( + await _inAppBrowserEventHandler!.shouldInterceptFetchRequest( + request, + ), + ); + } + return null; + case "onWindowFocus": + if (webviewParams != null && webviewParams!.onWindowFocus != null) + webviewParams!.onWindowFocus!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowFocus(); + return null; + case "onWindowBlur": + if (webviewParams != null && webviewParams!.onWindowBlur != null) + webviewParams!.onWindowBlur!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowBlur(); + return null; + case "onInjectedScriptLoaded": + String id = handlerData.args[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onLoadCallback != null) { + onLoadCallback(); + } + return null; + case "onInjectedScriptError": + String id = handlerData.args[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onErrorCallback != null) { + onErrorCallback(); + } + return null; + case "onWebMessageListenerPostMessageReceived": + Map arguments = handlerData.args[0] + .cast(); + String jsObjectName = arguments["jsObjectName"]; + // Find the corresponding web message listener + for (var listener in _webMessageListeners) { + if (listener.params.jsObjectName == jsObjectName) { + // Forward to the listener's channel + Map? messageMap = arguments["message"] + ?.cast(); + WebMessage? message = messageMap != null + ? WebMessage.fromMap(messageMap) + : null; + String? sourceOrigin = arguments["sourceOrigin"]; + bool isMainFrame = arguments["isMainFrame"] ?? true; + // Call the listener's onPostMessage callback via channel + listener.channel?.invokeMethod("onPostMessage", { + "message": message?.toMap(), + "sourceOrigin": sourceOrigin, + "isMainFrame": isMainFrame, + }); + break; + } + } + return null; + case "onWebMessagePortMessageReceived": + Map arguments = handlerData.args[0] + .cast(); + String webMessageChannelId = arguments["webMessageChannelId"]; + int index = arguments["index"]; + // Find the channel and forward the message + for (var webMessageChannel in _webMessageChannels) { + if (webMessageChannel.id == webMessageChannelId) { + Map? messageMap = arguments["message"] + ?.cast(); + WebMessage? message = messageMap != null + ? WebMessage.fromMap(messageMap) + : null; + webMessageChannel.internalChannel?.invokeMethod("onMessage", { + "index": index, + "message": message?.toMap(), + }); + break; + } + } + return null; + } + + if (_javaScriptHandlersMap.containsKey(handlerName)) { + // convert result to json + try { + var jsHandlerResult = null; + if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerCallback) { + jsHandlerResult = + await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerCallback)(handlerData.args); + } else if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerFunction) { + jsHandlerResult = + await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerFunction)(handlerData); + } else { + jsHandlerResult = await _javaScriptHandlersMap[handlerName]!(); + } + return jsonEncode(jsHandlerResult); + } catch (error, stacktrace) { + developer.log( + error.toString() + '\n' + stacktrace.toString(), + name: 'JavaScript Handler "$handlerName"', + ); + throw Exception(error.toString().replaceFirst('Exception: ', '')); + } + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + Future getUrl() async { + Map args = {}; + String? url = await channel?.invokeMethod('getUrl', args); + return url != null ? WebUri(url) : null; + } + + @override + Future getTitle() async { + Map args = {}; + return await channel?.invokeMethod('getTitle', args); + } + + @override + Future loadUrl({ + required URLRequest urlRequest, + @Deprecated('Use allowingReadAccessTo instead') + Uri? iosAllowingReadAccessTo, + WebUri? allowingReadAccessTo, + }) async { + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent( + 'allowingReadAccessTo', + () => + allowingReadAccessTo?.toString() ?? + iosAllowingReadAccessTo?.toString(), + ); + await channel?.invokeMethod('loadUrl', args); + } + + @override + Future loadData({ + required String data, + String mimeType = "text/html", + String encoding = "utf8", + WebUri? baseUrl, + @Deprecated('Use historyUrl instead') Uri? androidHistoryUrl, + WebUri? historyUrl, + @Deprecated('Use allowingReadAccessTo instead') + Uri? iosAllowingReadAccessTo, + WebUri? allowingReadAccessTo, + }) async { + Map args = {}; + args.putIfAbsent('data', () => data); + args.putIfAbsent('mimeType', () => mimeType); + args.putIfAbsent('encoding', () => encoding); + args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? 'about:blank'); + await channel?.invokeMethod('loadData', args); + } + + @override + Future postUrl({ + required WebUri url, + required Uint8List postData, + }) async { + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('postData', () => postData); + await channel?.invokeMethod('postUrl', args); + } + + @override + Future reload() async { + Map args = {}; + await channel?.invokeMethod('reload', args); + } + + @override + Future reloadFromOrigin() async { + Map args = {}; + await channel?.invokeMethod('reloadFromOrigin', args); + } + + @override + Future goBack() async { + Map args = {}; + await channel?.invokeMethod('goBack', args); + } + + @override + Future goForward() async { + Map args = {}; + await channel?.invokeMethod('goForward', args); + } + + @override + Future canGoBack() async { + Map args = {}; + return await channel?.invokeMethod('canGoBack', args) ?? false; + } + + @override + Future canGoForward() async { + Map args = {}; + return await channel?.invokeMethod('canGoForward', args) ?? false; + } + + @override + Future stopLoading() async { + Map args = {}; + await channel?.invokeMethod('stopLoading', args); + } + + @override + Future isLoading() async { + Map args = {}; + return await channel?.invokeMethod('isLoading', args) ?? false; + } + + @override + Future getProgress() async { + Map args = {}; + return await channel?.invokeMethod('getProgress', args); + } + + @override + Future loadFile({required String assetFilePath}) async { + Map args = {}; + args.putIfAbsent('assetFilePath', () => assetFilePath); + await channel?.invokeMethod('loadFile', args); + } + + @override + Future evaluateJavascript({ + required String source, + ContentWorld? contentWorld, + }) async { + Map args = {}; + args.putIfAbsent('source', () => source); + if (contentWorld != null) { + args.putIfAbsent('contentWorld', () => contentWorld.toMap()); + } + var result = await channel?.invokeMethod('evaluateJavascript', args); + if (result != null && result is String) { + try { + result = jsonDecode(result); + } catch (e) {} + } + return result; + } + + @override + Future callAsyncJavaScript({ + required String functionBody, + Map arguments = const {}, + ContentWorld? contentWorld, + }) async { + Map args = {}; + args.putIfAbsent('functionBody', () => functionBody); + // JSON encode arguments to handle complex types (arrays, nested objects) + // that WPE WebKit's GVariant a{sv} format doesn't support directly + args.putIfAbsent('arguments', () => jsonEncode(arguments)); + args.putIfAbsent('argumentKeys', () => arguments.keys.toList()); + if (contentWorld != null) { + args.putIfAbsent('contentWorld', () => contentWorld.toMap()); + } + + // Native returns a JSON string: {"value": ..., "error": ...} + String? jsonResult = await channel?.invokeMethod( + 'callAsyncJavaScript', + args, + ); + + if (jsonResult != null) { + try { + Map result = jsonDecode(jsonResult); + return CallAsyncJavaScriptResult( + value: result['value'], + error: result['error'], + ); + } catch (e) { + return CallAsyncJavaScriptResult( + value: null, + error: 'Failed to decode result: $e', + ); + } + } + return null; + } + + @override + Future injectJavascriptFileFromUrl({ + required WebUri urlFile, + ScriptHtmlTagAttributes? scriptHtmlTagAttributes, + }) async { + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'scriptHtmlTagAttributes', + () => scriptHtmlTagAttributes?.toMap(), + ); + await channel?.invokeMethod('injectJavascriptFileFromUrl', args); + } + + @override + Future injectCSSCode({required String source}) async { + Map args = {}; + args.putIfAbsent('source', () => source); + await channel?.invokeMethod('injectCSSCode', args); + } + + @override + Future injectCSSFileFromUrl({ + required WebUri urlFile, + CSSLinkHtmlTagAttributes? cssLinkHtmlTagAttributes, + }) async { + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'cssLinkHtmlTagAttributes', + () => cssLinkHtmlTagAttributes?.toMap(), + ); + await channel?.invokeMethod('injectCSSFileFromUrl', args); + } + + @override + Future getHtml() async { + Map args = {}; + return await channel?.invokeMethod('getHtml', args); + } + + @override + Future getSelectedText() async { + Map args = {}; + return await channel?.invokeMethod('getSelectedText', args); + } + + @override + Future isSecureContext() async { + Map args = {}; + return await channel?.invokeMethod('isSecureContext', args) ?? false; + } + + @override + Future getCertificate() async { + Map args = {}; + Map? result = await channel?.invokeMethod( + 'getCertificate', + args, + ); + if (result != null) { + return SslCertificate.fromMap(result.cast()); + } + return null; + } + + @override + Future getHitTestResult() async { + Map args = {}; + Map? result = await channel?.invokeMethod( + 'getHitTestResult', + args, + ); + if (result != null) { + return InAppWebViewHitTestResult.fromMap(result.cast()); + } + return null; + } + + @override + Future clearAllCache({bool includeDiskFiles = true}) async { + Map args = {}; + args.putIfAbsent('includeDiskFiles', () => includeDiskFiles); + await _staticChannel.invokeMethod('clearAllCache', args); + } + + @override + Future setJavaScriptBridgeName(String bridgeName) async { + assert( + RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName), + 'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.', + ); + Map args = {}; + args.putIfAbsent('bridgeName', () => bridgeName); + await _staticChannel.invokeMethod('setJavaScriptBridgeName', args); + } + + @override + Future getJavaScriptBridgeName() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getJavaScriptBridgeName', + args, + ) ?? + 'flutter_inappwebview'; + } + + @override + Future disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async { + Map args = {}; + args.putIfAbsent('keepAliveId', () => keepAlive.id); + await _staticChannel.invokeMethod('disposeKeepAlive', args); + _keepAliveMap[keepAlive] = null; + } + + @override + Future get tRexRunnerHtml async => await rootBundle.loadString( + 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html', + ); + + @override + Future get tRexRunnerCss async => await rootBundle.loadString( + 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css', + ); + + @override + dynamic getViewId() { + return id; + } + + @override + Future handlesURLScheme(String urlScheme) async { + Map args = {}; + args.putIfAbsent('urlScheme', () => urlScheme); + return await _staticChannel.invokeMethod('handlesURLScheme', args) ?? + false; + } + + @override + Future canScrollVertically() async { + Map args = {}; + return await channel?.invokeMethod('canScrollVertically', args) ?? + false; + } + + @override + Future canScrollHorizontally() async { + Map args = {}; + return await channel?.invokeMethod('canScrollHorizontally', args) ?? + false; + } + + @override + Future getZoomScale() async { + Map args = {}; + return await channel?.invokeMethod('getZoomScale', args); + } + + @override + Future zoomBy({ + required double zoomFactor, + @Deprecated('Use animated instead') bool? iosAnimated, + bool animated = false, + }) async { + Map args = {}; + args.putIfAbsent('zoomFactor', () => zoomFactor); + await channel?.invokeMethod('setZoomScale', args); + } + + @override + Future scrollTo({ + required int x, + required int y, + bool animated = false, + }) async { + Map args = {}; + args.putIfAbsent('x', () => x); + args.putIfAbsent('y', () => y); + args.putIfAbsent('animated', () => animated); + await channel?.invokeMethod('scrollTo', args); + } + + @override + Future scrollBy({ + required int x, + required int y, + bool animated = false, + }) async { + Map args = {}; + args.putIfAbsent('x', () => x); + args.putIfAbsent('y', () => y); + args.putIfAbsent('animated', () => animated); + await channel?.invokeMethod('scrollBy', args); + } + + @override + Future getScrollX() async { + Map args = {}; + return await channel?.invokeMethod('getScrollX', args); + } + + @override + Future getScrollY() async { + Map args = {}; + return await channel?.invokeMethod('getScrollY', args); + } + + @override + Future getSettings() async { + Map args = {}; + Map? settings = await channel?.invokeMethod( + 'getSettings', + args, + ); + if (settings != null) { + settings = settings.cast(); + return InAppWebViewSettings.fromMap(settings as Map); + } + return null; + } + + @override + Future setSettings({required InAppWebViewSettings settings}) async { + Map args = {}; + args.putIfAbsent('settings', () => settings.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + void addJavaScriptHandler({ + required String handlerName, + required Function callback, + }) { + assert( + !kJavaScriptHandlerForbiddenNames.contains(handlerName), + '"$handlerName" is a reserved name and cannot be used as a JavaScript handler name.', + ); + _javaScriptHandlersMap[handlerName] = callback; + } + + @override + Function? removeJavaScriptHandler({required String handlerName}) { + return _javaScriptHandlersMap.remove(handlerName); + } + + @override + bool hasJavaScriptHandler({required String handlerName}) { + return _javaScriptHandlersMap.containsKey(handlerName); + } + + @override + Future addUserScript({required UserScript userScript}) async { + // Note: WebKitGTK doesn't support content worlds like WKWebView, + // so we ignore the contentWorld parameter + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + await channel?.invokeMethod('addUserScript', args); + + _userScripts[userScript.injectionTime]?.add(userScript); + } + + @override + Future addUserScripts({required List userScripts}) async { + for (var userScript in userScripts) { + await addUserScript(userScript: userScript); + } + } + + @override + Future removeUserScript({required UserScript userScript}) async { + var index = + _userScripts[userScript.injectionTime]?.indexOf(userScript) ?? -1; + if (index == -1) { + return false; + } + + Map args = {}; + args.putIfAbsent('index', () => index); + args.putIfAbsent( + 'injectionTime', + () => userScript.injectionTime.toNativeValue(), + ); + await channel?.invokeMethod('removeUserScript', args); + + _userScripts[userScript.injectionTime]?.remove(userScript); + return true; + } + + @override + Future removeUserScriptsByGroupName({required String groupName}) async { + Map args = {}; + args.putIfAbsent('groupName', () => groupName); + await channel?.invokeMethod('removeUserScriptsByGroupName', args); + + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.removeWhere( + (element) => element.groupName == groupName, + ); + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.removeWhere( + (element) => element.groupName == groupName, + ); + } + + @override + Future removeUserScripts({ + required List userScripts, + }) async { + for (var userScript in userScripts) { + await removeUserScript(userScript: userScript); + } + } + + @override + Future removeAllUserScripts() async { + await channel?.invokeMethod('removeAllUserScripts', {}); + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.clear(); + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); + } + + /// Returns all user scripts that have been added. + /// This is a Linux-specific method for accessing the user scripts cache. + Future> getUserScripts() async { + List result = []; + result.addAll( + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] ?? [], + ); + result.addAll(_userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] ?? []); + return result; + } + + @override + bool hasUserScript({required UserScript userScript}) { + return _userScripts[userScript.injectionTime]?.contains(userScript) ?? + false; + } + + @override + Future addWebMessageListener( + PlatformWebMessageListener webMessageListener, + ) async { + assert( + !_webMessageListeners.contains(webMessageListener), + "${webMessageListener} was already added.", + ); + assert( + !_webMessageListenerObjNames.contains( + webMessageListener.params.jsObjectName, + ), + "jsObjectName ${webMessageListener.params.jsObjectName} was already added.", + ); + _webMessageListeners.add(webMessageListener as LinuxWebMessageListener); + _webMessageListenerObjNames.add(webMessageListener.params.jsObjectName); + + Map args = {}; + args.putIfAbsent('webMessageListener', () => webMessageListener.toMap()); + await channel?.invokeMethod('addWebMessageListener', args); + } + + @override + bool hasWebMessageListener(PlatformWebMessageListener webMessageListener) { + return _webMessageListeners.contains(webMessageListener) || + _webMessageListenerObjNames.contains( + webMessageListener.params.jsObjectName, + ); + } + + @override + Future createWebMessageChannel() async { + Map args = {}; + Map? result = (await channel?.invokeMethod( + 'createWebMessageChannel', + args, + ))?.cast(); + final webMessageChannel = LinuxWebMessageChannel.static().fromMap(result); + if (webMessageChannel != null) { + _webMessageChannels.add(webMessageChannel); + } + return webMessageChannel; + } + + @override + Future postWebMessage({ + required WebMessage message, + WebUri? targetOrigin, + }) async { + if (targetOrigin == null) { + targetOrigin = WebUri(''); + } + Map args = {}; + args.putIfAbsent('message', () => message.toMap()); + args.putIfAbsent('targetOrigin', () => targetOrigin.toString()); + await channel?.invokeMethod('postWebMessage', args); + } + + @override + Future getOriginalUrl() async { + Map args = {}; + String? url = await channel?.invokeMethod('getOriginalUrl', args); + return url != null ? WebUri(url) : null; + } + + /// Sets the zoom level of the WebView. + /// This is a Linux-specific method that directly sets the zoom level. + /// For cross-platform code, use [zoomBy] instead. + Future setZoomScale({ + required double zoomScale, + @Deprecated('Use animated instead') bool? iosAnimated, + bool animated = false, + }) async { + Map args = {}; + args.putIfAbsent('zoomScale', () => zoomScale); + args.putIfAbsent('animated', () => animated); + await channel?.invokeMethod('setZoomScale', args); + } + + @override + Future isInFullscreen() async { + return await channel?.invokeMethod('isInFullscreen', {}) ?? false; + } + + @override + Future requestEnterFullscreen() async { + await channel?.invokeMethod('requestEnterFullscreen', {}); + } + + @override + Future requestExitFullscreen() async { + await channel?.invokeMethod('requestExitFullscreen', {}); + } + + @override + Future setVisible({required bool visible}) async { + await channel?.invokeMethod('setVisible', visible); + } + + @override + Future setTargetRefreshRate({required int rate}) async { + await channel?.invokeMethod('setTargetRefreshRate', rate); + } + + @override + Future getTargetRefreshRate() async { + return await channel?.invokeMethod('getTargetRefreshRate', {}) ?? 0; + } + + @override + Future getScreenScale() async { + return await channel?.invokeMethod('getScreenScale', {}) ?? 1.0; + } + + @override + Future setScreenScale({required double scale}) async { + await channel?.invokeMethod('setScreenScale', scale); + } + + @override + Future isVisible() async { + return await channel?.invokeMethod('isVisible', {}) ?? false; + } + + @override + Future requestPointerLock() async { + return await channel?.invokeMethod('requestPointerLock', {}) ?? false; + } + + @override + Future requestPointerUnlock() async { + return await channel?.invokeMethod('requestPointerUnlock', {}) ?? + false; + } + + @override + Future takeScreenshot({ + ScreenshotConfiguration? screenshotConfiguration, + }) async { + Map args = {}; + args.putIfAbsent( + 'screenshotConfiguration', + () => screenshotConfiguration?.toMap(), + ); + return await channel?.invokeMethod('takeScreenshot', args); + } + + @override + Future saveState() async { + Map args = {}; + return await channel?.invokeMethod('saveState', args); + } + + @override + Future restoreState(Uint8List state) async { + Map args = {}; + args.putIfAbsent('state', () => state); + return await channel?.invokeMethod('restoreState', args) ?? false; + } + + @override + Future saveWebArchive({ + required String filePath, + bool autoname = false, + }) async { + if (!autoname) { + assert( + WebArchiveFormat.MHT.isSupported() && + filePath.endsWith("." + WebArchiveFormat.MHT.toNativeValue()!), + ); + } + + Map args = {}; + args.putIfAbsent('filePath', () => filePath); + args.putIfAbsent('autoname', () => autoname); + return await channel?.invokeMethod('saveWebArchive', args); + } + + @override + Future getContentHeight() async { + Map args = {}; + return await channel?.invokeMethod('getContentHeight', args); + } + + @override + Future getContentWidth() async { + Map args = {}; + return await channel?.invokeMethod('getContentWidth', args); + } + + @override + Future getCopyBackForwardList() async { + Map args = {}; + Map? result = await channel?.invokeMethod( + 'getCopyBackForwardList', + args, + ); + if (result != null) { + return WebHistory.fromMap(result.cast()); + } + return null; + } + + @override + Future goBackOrForward({required int steps}) async { + Map args = {}; + args.putIfAbsent('steps', () => steps); + await channel?.invokeMethod('goBackOrForward', args); + } + + @override + Future canGoBackOrForward({required int steps}) async { + Map args = {}; + args.putIfAbsent('steps', () => steps); + return await channel?.invokeMethod('canGoBackOrForward', args) ?? + false; + } + + @override + Future> getFavicons() async { + List favicons = []; + + var webviewUrl = await getUrl(); + + if (webviewUrl == null) { + return favicons; + } + + String? manifestUrl; + + var html = await getHtml(); + if (html == null || html.isEmpty) { + return favicons; + } + var assetPathBase; + + if (webviewUrl.isScheme("file")) { + var assetPathSplit = webviewUrl.toString().split("/flutter_assets/"); + assetPathBase = assetPathSplit[0] + "/flutter_assets/"; + } + + InAppWebViewSettings? settings = await getSettings(); + if (settings != null && settings.javaScriptEnabled == true) { + var jsResult = await evaluateJavascript( + source: """ +(function() { + var linkNodes = document.head.getElementsByTagName("link"); + var links = []; + for (var i = 0; i < linkNodes.length; i++) { + var linkNode = linkNodes[i]; + if (linkNode.rel === 'manifest') { + links.push( + { + rel: linkNode.rel, + href: linkNode.href, + sizes: null + } + ); + } else if (linkNode.rel != null && linkNode.rel.indexOf('icon') >= 0) { + links.push( + { + rel: linkNode.rel, + href: linkNode.href, + sizes: linkNode.sizes != null && linkNode.sizes.value != "" ? linkNode.sizes.value : null + } + ); + } + } + return links; +})(); +""", + ); + List> links = []; + if (jsResult is List) { + links = jsResult.cast>(); + } + for (var link in links) { + if (link["rel"] == "manifest") { + manifestUrl = link["href"]; + if (!_isUrlAbsolute(manifestUrl!)) { + if (manifestUrl.startsWith("/")) { + manifestUrl = manifestUrl.substring(1); + } + manifestUrl = + ((assetPathBase == null) + ? webviewUrl.scheme + "://" + webviewUrl.host + "/" + : assetPathBase) + + manifestUrl; + } + continue; + } + favicons.addAll( + _createFavicons( + webviewUrl, + assetPathBase, + link["href"], + link["rel"], + link["sizes"], + false, + ), + ); + } + } + + // try to get /favicon.ico + try { + HttpClient client = HttpClient(); + var faviconUrl = + webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico"; + var faviconUri = WebUri(faviconUrl); + var headRequest = await client.headUrl(faviconUri); + var headResponse = await headRequest.close(); + if (headResponse.statusCode == 200) { + favicons.add(Favicon(url: faviconUri, rel: "shortcut icon")); + } + } catch (e) { + developer.log( + "/favicon.ico file not found: " + e.toString(), + name: runtimeType.toString(), + ); + } + + // try to get the manifest file + HttpClientRequest? manifestRequest; + HttpClientResponse? manifestResponse; + bool manifestFound = false; + if (manifestUrl == null) { + manifestUrl = + webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json"; + } + try { + HttpClient client = HttpClient(); + manifestRequest = await client.getUrl(Uri.parse(manifestUrl)); + manifestResponse = await manifestRequest.close(); + manifestFound = + manifestResponse.statusCode == 200 && + manifestResponse.headers.contentType?.mimeType == "application/json"; + } catch (e) { + developer.log( + "Manifest file not found: " + e.toString(), + name: this.runtimeType.toString(), + ); + } + + if (manifestFound) { + try { + Map manifest = json.decode( + await manifestResponse!.transform(Utf8Decoder()).join(), + ); + if (manifest.containsKey("icons")) { + for (Map icon in manifest["icons"]) { + favicons.addAll( + _createFavicons( + webviewUrl, + assetPathBase, + icon["src"], + icon["rel"], + icon["sizes"], + true, + ), + ); + } + } + } catch (e) { + developer.log( + "Cannot get favicons from Manifest file. It might not have a valid format: " + + e.toString(), + error: e, + name: runtimeType.toString(), + ); + } + } + + return favicons; + } + + bool _isUrlAbsolute(String url) { + return url.startsWith("http://") || url.startsWith("https://"); + } + + List _createFavicons( + WebUri url, + String? assetPathBase, + String urlIcon, + String? rel, + String? sizes, + bool isManifest, + ) { + List favicons = []; + + List urlSplit = urlIcon.split("/"); + if (!_isUrlAbsolute(urlIcon)) { + if (urlIcon.startsWith("/")) { + urlIcon = urlIcon.substring(1); + } + urlIcon = + ((assetPathBase == null) + ? url.scheme + "://" + url.host + "/" + : assetPathBase) + + urlIcon; + } + if (isManifest) { + rel = (sizes != null) + ? urlSplit[urlSplit.length - 1] + .replaceFirst("-" + sizes, "") + .split(" ")[0] + .split(".")[0] + : null; + } + if (sizes != null && sizes.isNotEmpty && sizes != "any") { + List sizesSplit = sizes.split(" "); + for (String size in sizesSplit) { + int width = int.parse(size.split("x")[0]); + int height = int.parse(size.split("x")[1]); + favicons.add( + Favicon(url: WebUri(urlIcon), rel: rel, width: width, height: height), + ); + } + } else { + favicons.add( + Favicon(url: WebUri(urlIcon), rel: rel, width: null, height: null), + ); + } + + return favicons; + } + + @override + Future pauseAllMediaPlayback() async { + Map args = {}; + await channel?.invokeMethod('pauseAllMediaPlayback', args); + } + + @override + Future setAllMediaPlaybackSuspended({required bool suspended}) async { + Map args = {}; + args.putIfAbsent('suspended', () => suspended); + await channel?.invokeMethod('setAllMediaPlaybackSuspended', args); + } + + @override + Future closeAllMediaPresentations() async { + Map args = {}; + await channel?.invokeMethod('closeAllMediaPresentations', args); + } + + @override + Future requestMediaPlaybackState() async { + Map args = {}; + int? result = await channel?.invokeMethod( + 'requestMediaPlaybackState', + args, + ); + if (result != null) { + return MediaPlaybackState.fromNativeValue(result); + } + return null; + } + + @override + Future getCameraCaptureState() async { + Map args = {}; + int? result = await channel?.invokeMethod( + 'getCameraCaptureState', + args, + ); + if (result != null) { + return MediaCaptureState.fromNativeValue(result); + } + return null; + } + + @override + Future setCameraCaptureState({required MediaCaptureState state}) async { + Map args = {}; + args.putIfAbsent('state', () => state.toNativeValue()); + await channel?.invokeMethod('setCameraCaptureState', args); + } + + @override + Future getMicrophoneCaptureState() async { + Map args = {}; + int? result = await channel?.invokeMethod( + 'getMicrophoneCaptureState', + args, + ); + if (result != null) { + return MediaCaptureState.fromNativeValue(result); + } + return null; + } + + @override + Future setMicrophoneCaptureState({ + required MediaCaptureState state, + }) async { + Map args = {}; + args.putIfAbsent('state', () => state.toNativeValue()); + await channel?.invokeMethod('setMicrophoneCaptureState', args); + } + + @override + Future> getMetaTags() async { + List metaTags = []; + + List>? metaTagList = (await evaluateJavascript( + source: """ +(function() { + var metaTags = []; + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + + var otherAttributes = metaTagNode.getAttributeNames(); + var nameIndex = otherAttributes.indexOf("name"); + if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1); + var contentIndex = otherAttributes.indexOf("content"); + if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1); + + var attrs = []; + for (var j = 0; j < otherAttributes.length; j++) { + var otherAttribute = otherAttributes[j]; + attrs.push( + { + name: otherAttribute, + value: metaTagNode.getAttribute(otherAttribute) + } + ); + } + + metaTags.push( + { + name: metaTagNode.name, + content: metaTagNode.content, + attrs: attrs + } + ); + } + return metaTags; +})(); + """, + ))?.cast>(); + + if (metaTagList == null) { + return metaTags; + } + + for (var metaTag in metaTagList) { + var attrs = []; + + for (var metaTagAttr in metaTag["attrs"]) { + attrs.add( + MetaTagAttribute( + name: metaTagAttr["name"], + value: metaTagAttr["value"], + ), + ); + } + + metaTags.add( + MetaTag( + name: metaTag["name"], + content: metaTag["content"], + attrs: attrs, + ), + ); + } + + return metaTags; + } + + @override + Future getMetaThemeColor() async { + Map args = {}; + String? hexColor = await channel?.invokeMethod( + 'getMetaThemeColor', + args, + ); + if (hexColor == null || hexColor.isEmpty) { + return null; + } + // Parse hex color string like #RRGGBB or #RRGGBBAA + return UtilColor.fromHex(hexColor); + } + + @override + Future isPlayingAudio() async { + Map args = {}; + return await channel?.invokeMethod('isPlayingAudio', args) ?? false; + } + + @override + Future isMuted() async { + Map args = {}; + return await channel?.invokeMethod('isMuted', args) ?? false; + } + + @override + Future setMuted({required bool muted}) async { + Map args = {}; + args.putIfAbsent('muted', () => muted); + await channel?.invokeMethod('setMuted', args); + } + + @override + Future terminateWebProcess() async { + Map args = {}; + await channel?.invokeMethod('terminateWebProcess', args); + } + + @override + Future clearFocus() async { + Map args = {}; + await channel?.invokeMethod('clearFocus', args); + } + + @override + Future requestFocus({ + FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect, + }) async { + Map args = {}; + return await channel?.invokeMethod('requestFocus', args); + } + + @override + void dispose({bool isKeepAlive = false}) { + if (!isKeepAlive) { + for (final webMessageListener in _webMessageListeners) { + webMessageListener.dispose(); + } + _webMessageListeners.clear(); + _webMessageListenerObjNames.clear(); + for (final webMessageChannel in _webMessageChannels) { + webMessageChannel.dispose(); + } + _webMessageChannels.clear(); + } + webStorage.dispose(); + disposeChannel(removeMethodCallHandler: !isKeepAlive); + } +} + +extension InternalInAppWebViewController on LinuxInAppWebViewController { + get handleMethod => _handleMethod; +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/key_mappings.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/key_mappings.dart new file mode 100644 index 00000000..25bd7c3d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/key_mappings.dart @@ -0,0 +1,145 @@ +/// Key mapping tables for WPE WebKit keyboard input +/// +/// WPE WebKit expects: +/// - key_code: XKB keysym (Unicode for printable chars, 0xFF00+ for special keys) +/// - hardware_key_code: X11 keycode (evdev scancode + 8) + +import 'package:flutter/services.dart'; + +/// USB HID usage code to Linux evdev scancode mapping. +/// Based on Linux kernel's hid-input.c hid_keyboard[] table. +/// Reference: https://www.usb.org/sites/default/files/hut1_3_0.pdf (page 89) +const kUsbHidToEvdev = { + // Letters (a-z) + 0x04: 30, 0x05: 48, 0x06: 46, 0x07: 32, 0x08: 18, 0x09: 33, + 0x0a: 34, 0x0b: 35, 0x0c: 23, 0x0d: 36, 0x0e: 37, 0x0f: 38, + 0x10: 50, 0x11: 49, 0x12: 24, 0x13: 25, 0x14: 16, 0x15: 19, + 0x16: 31, 0x17: 20, 0x18: 22, 0x19: 47, 0x1a: 17, 0x1b: 45, + 0x1c: 21, 0x1d: 44, + // Numbers (1-0) + 0x1e: 2, 0x1f: 3, 0x20: 4, 0x21: 5, 0x22: 6, + 0x23: 7, 0x24: 8, 0x25: 9, 0x26: 10, 0x27: 11, + // Common keys + 0x28: 28, // Enter + 0x29: 1, // Escape + 0x2a: 14, // Backspace + 0x2b: 15, // Tab + 0x2c: 57, // Space + // Symbols + 0x2d: 12, 0x2e: 13, 0x2f: 26, 0x30: 27, 0x31: 43, + 0x33: 39, 0x34: 40, 0x35: 41, 0x36: 51, 0x37: 52, 0x38: 53, + // Lock keys + 0x39: 58, // CapsLock + // Function keys (F1-F12) + 0x3a: 59, 0x3b: 60, 0x3c: 61, 0x3d: 62, 0x3e: 63, 0x3f: 64, + 0x40: 65, 0x41: 66, 0x42: 67, 0x43: 68, 0x44: 87, 0x45: 88, + // System keys + 0x46: 99, 0x47: 70, 0x48: 119, + // Navigation + 0x49: 110, 0x4a: 102, 0x4b: 104, 0x4c: 111, 0x4d: 107, 0x4e: 109, + // Arrow keys + 0x4f: 106, 0x50: 105, 0x51: 108, 0x52: 103, + // Keypad + 0x53: 69, 0x54: 98, 0x55: 55, 0x56: 74, 0x57: 78, 0x58: 96, + 0x59: 79, 0x5a: 80, 0x5b: 81, 0x5c: 75, 0x5d: 76, 0x5e: 77, + 0x5f: 71, 0x60: 72, 0x61: 73, 0x62: 82, 0x63: 83, + // Modifiers + 0xe0: 29, 0xe1: 42, 0xe2: 56, 0xe3: 125, + 0xe4: 97, 0xe5: 54, 0xe6: 100, 0xe7: 126, +}; + +/// Convert USB HID usage code to X11 keycode. +/// X11 keycode = evdev scancode + 8 +int usbHidToX11Keycode(int usbHid) { + final evdev = kUsbHidToEvdev[usbHid] ?? 0; + return evdev + 8; +} + +/// X11 keysym values for special keys. +/// For printable characters, the Unicode code point IS the keysym. +int getX11Keysym(LogicalKeyboardKey key, String? character) { + // For printable characters, use Unicode code point directly + if (character != null && character.isNotEmpty) { + final codeUnit = character.codeUnitAt(0); + if (codeUnit >= 0x20 && codeUnit < 0x7f) { + return codeUnit; // ASCII printable -> Unicode keysym + } + // Extended Latin and other Unicode characters (keysym = Unicode codepoint) + if (codeUnit >= 0x00a0 && codeUnit <= 0xffff) { + return codeUnit; + } + } + + // Special keys use X11 keysym constants + final keyId = key.keyId; + + // Check explicit mapping first + final mapped = _specialKeyToKeysym[keyId]; + if (mapped != null) { + return mapped; + } + + // Function keys (F1-F24): XK_F1 = 0xffbe, XK_F13 = 0xffc8 + if (keyId >= LogicalKeyboardKey.f1.keyId && + keyId <= LogicalKeyboardKey.f24.keyId) { + return 0xffbe + (keyId - LogicalKeyboardKey.f1.keyId); + } + + // Try keyLabel for printable characters that weren't caught above + final label = key.keyLabel; + if (label.length == 1) { + final codeUnit = label.codeUnitAt(0); + // ASCII printable or extended Latin + if ((codeUnit >= 0x20 && codeUnit < 0x7f) || + (codeUnit >= 0x00a0 && codeUnit <= 0xffff)) { + return codeUnit; + } + } + + // Return 0 for unknown keys - safer than truncating keyId which could + // collide with valid keysyms or produce unexpected behavior + return 0; +} + +const _specialKeyToKeysym = { + // Control keys + 0x100000008: 0xff08, // Backspace (XK_BackSpace) + 0x100000009: 0xff09, // Tab (XK_Tab) + 0x10000000d: 0xff0d, // Enter (XK_Return) + 0x10000001b: 0xff1b, // Escape (XK_Escape) + 0x10000007f: 0xffff, // Delete (XK_Delete) + + // Arrow keys - Flutter LogicalKeyboardKey.arrow*.keyId values + 0x100000301: 0xff54, // ArrowDown (XK_Down) + 0x100000302: 0xff51, // ArrowLeft (XK_Left) + 0x100000303: 0xff53, // ArrowRight (XK_Right) + 0x100000304: 0xff52, // ArrowUp (XK_Up) + + // Navigation keys - Flutter LogicalKeyboardKey.*.keyId values + 0x100000305: 0xff57, // End (XK_End) + 0x100000306: 0xff50, // Home (XK_Home) + 0x100000307: 0xff56, // PageDown (XK_Page_Down) + 0x100000308: 0xff55, // PageUp (XK_Page_Up) + 0x100000407: 0xff63, // Insert (XK_Insert) + + // Lock keys + 0x100000104: 0xffe5, // CapsLock (XK_Caps_Lock) + 0x10000010a: 0xff7f, // NumLock (XK_Num_Lock) + 0x10000010c: 0xff14, // ScrollLock (XK_Scroll_Lock) + + // Modifier keys (note: 0x200000xxx range) + 0x200000100: 0xffe3, // ControlLeft (XK_Control_L) + 0x200000101: 0xffe4, // ControlRight (XK_Control_R) + 0x200000102: 0xffe1, // ShiftLeft (XK_Shift_L) + 0x200000103: 0xffe2, // ShiftRight (XK_Shift_R) + 0x200000104: 0xffe9, // AltLeft (XK_Alt_L) + 0x200000105: 0xffea, // AltRight (XK_Alt_R) + 0x200000106: 0xffeb, // MetaLeft (XK_Super_L) + 0x200000107: 0xffec, // MetaRight (XK_Super_R) + + // Misc + 0x20: 0x0020, // Space + 0x100000505: 0xff67, // ContextMenu (XK_Menu) + 0x100000509: 0xff13, // Pause (XK_Pause) + 0x100000608: 0xff61, // PrintScreen (XK_Print) +}; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/main.dart new file mode 100644 index 00000000..b83b0611 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/in_app_webview/main.dart @@ -0,0 +1,3 @@ +export 'in_app_webview_controller.dart' hide InternalInAppWebViewController; +export 'in_app_webview.dart'; +export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/inappwebview_platform.dart b/.vendor/flutter_inappwebview_linux/lib/src/inappwebview_platform.dart new file mode 100644 index 00000000..d811241d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/inappwebview_platform.dart @@ -0,0 +1,616 @@ +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'cookie_manager/cookie_manager.dart'; +import 'find_interaction/find_interaction_controller.dart'; +import 'http_auth_credentials_database.dart'; +import 'in_app_browser/in_app_browser.dart'; +import 'in_app_webview/in_app_webview.dart'; +import 'in_app_webview/in_app_webview_controller.dart'; +import 'in_app_webview/headless_in_app_webview.dart'; +import 'proxy_controller/proxy_controller.dart'; +import 'web_message/web_message_channel.dart'; +import 'web_message/web_message_listener.dart'; +import 'web_message/web_message_port.dart'; +import 'web_storage/web_storage.dart'; +import 'web_storage/web_storage_manager.dart'; +import 'webview_environment/webview_environment.dart'; + +/// Implementation of [InAppWebViewPlatform] using WPE WebKit. +class LinuxInAppWebViewPlatform extends InAppWebViewPlatform { + /// Registers this class as the default instance of [InAppWebViewPlatform]. + static void registerWith() { + InAppWebViewPlatform.instance = LinuxInAppWebViewPlatform(); + } + + /// Creates a new [LinuxInAppWebViewController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + LinuxInAppWebViewController createPlatformInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params, + ) { + return LinuxInAppWebViewController(params); + } + + /// Creates a new empty [LinuxInAppWebViewController] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + LinuxInAppWebViewController createPlatformInAppWebViewControllerStatic() { + return LinuxInAppWebViewController.static(); + } + + /// Creates a new [LinuxInAppWebViewWidget]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + LinuxInAppWebViewWidget createPlatformInAppWebViewWidget( + PlatformInAppWebViewWidgetCreationParams params, + ) { + return LinuxInAppWebViewWidget(params); + } + + /// Creates a new empty [LinuxInAppWebViewWidget] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + LinuxInAppWebViewWidget createPlatformInAppWebViewWidgetStatic() { + return LinuxInAppWebViewWidget.static(); + } + + /// Creates a new empty [PlatformCookieManager] to access static methods. + @override + PlatformCookieManager createPlatformCookieManagerStatic() { + return LinuxCookieManager.static(); + } + + /// Creates a new [LinuxCookieManager]. + @override + LinuxCookieManager createPlatformCookieManager( + PlatformCookieManagerCreationParams params, + ) { + return LinuxCookieManager(params); + } + + /// Creates a new empty [PlatformWebViewEnvironment] to access static methods. + @override + PlatformWebViewEnvironment createPlatformWebViewEnvironmentStatic() { + return LinuxWebViewEnvironment.static(); + } + + /// Creates a new [LinuxWebViewEnvironment]. + @override + LinuxWebViewEnvironment createPlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params, + ) { + return LinuxWebViewEnvironment(params); + } + + // ************************************************************************ // + // Create static instances of unsupported classes to be able to call // + // isClassSupported, isMethodSupported, isPropertySupported, etc. // + // static methods without throwing a missing platform implementation // + // exception. // + // ************************************************************************ // + + /// Creates a new empty [PlatformChromeSafariBrowser] to access static methods. + @override + PlatformChromeSafariBrowser createPlatformChromeSafariBrowserStatic() { + return _PlatformChromeSafariBrowser.static(); + } + + /// Creates a new [LinuxHttpAuthCredentialDatabase]. + @override + LinuxHttpAuthCredentialDatabase createPlatformHttpAuthCredentialDatabase( + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) { + return LinuxHttpAuthCredentialDatabase(params); + } + + /// Creates a new empty [LinuxHttpAuthCredentialDatabase] to access static methods. + @override + LinuxHttpAuthCredentialDatabase + createPlatformHttpAuthCredentialDatabaseStatic() { + return LinuxHttpAuthCredentialDatabase.static(); + } + + /// Creates a new empty [LinuxInAppBrowser] to access static methods. + @override + LinuxInAppBrowser createPlatformInAppBrowserStatic() { + return LinuxInAppBrowser.static(); + } + + /// Creates a new [LinuxInAppBrowser]. + @override + LinuxInAppBrowser createPlatformInAppBrowser( + PlatformInAppBrowserCreationParams params, + ) { + return LinuxInAppBrowser(params); + } + + /// Creates a new empty [PlatformHeadlessInAppWebView] to access static methods. + @override + PlatformHeadlessInAppWebView createPlatformHeadlessInAppWebViewStatic() { + return LinuxHeadlessInAppWebView.static(); + } + + /// Creates a new [LinuxHeadlessInAppWebView]. + @override + LinuxHeadlessInAppWebView createPlatformHeadlessInAppWebView( + PlatformHeadlessInAppWebViewCreationParams params, + ) { + return LinuxHeadlessInAppWebView(params); + } + + /// Creates a new empty [PlatformProcessGlobalConfig] to access static methods. + @override + PlatformProcessGlobalConfig createPlatformProcessGlobalConfigStatic() { + return _PlatformProcessGlobalConfig.static(); + } + + /// Creates a new empty [PlatformProxyController] to access static methods. + @override + PlatformProxyController createPlatformProxyControllerStatic() { + return LinuxProxyController.static(); + } + + /// Creates a new [LinuxProxyController]. + @override + LinuxProxyController createPlatformProxyController( + PlatformProxyControllerCreationParams params, + ) { + return LinuxProxyController(params); + } + + /// Creates a new empty [PlatformServiceWorkerController] to access static methods. + @override + PlatformServiceWorkerController + createPlatformServiceWorkerControllerStatic() { + return _PlatformServiceWorkerController.static(); + } + + /// Creates a new empty [PlatformTracingController] to access static methods. + @override + PlatformTracingController createPlatformTracingControllerStatic() { + return _PlatformTracingController.static(); + } + + /// Creates a new empty [PlatformFindInteractionController] to access static methods. + @override + PlatformFindInteractionController + createPlatformFindInteractionControllerStatic() { + return LinuxFindInteractionController.static(); + } + + /// Creates a new [LinuxFindInteractionController]. + @override + LinuxFindInteractionController createPlatformFindInteractionController( + PlatformFindInteractionControllerCreationParams params, + ) { + return LinuxFindInteractionController(params); + } + + /// Creates a new empty [PlatformPrintJobController] to access static methods. + @override + PlatformPrintJobController createPlatformPrintJobControllerStatic() { + return _PlatformPrintJobController.static(); + } + + /// Creates a new empty [PlatformPullToRefreshController] to access static methods. + @override + PlatformPullToRefreshController + createPlatformPullToRefreshControllerStatic() { + return _PlatformPullToRefreshController.static(); + } + + /// Creates a new empty [PlatformWebAuthenticationSession] to access static methods. + @override + PlatformWebAuthenticationSession + createPlatformWebAuthenticationSessionStatic() { + return _PlatformWebAuthenticationSession.static(); + } + + /// Creates a new empty [PlatformWebNotificationController] to access static methods. + @override + PlatformWebNotificationController + createPlatformWebNotificationControllerStatic() { + return _PlatformWebNotificationController.static(); + } + + /// Creates a new empty [LinuxWebMessageChannel] to access static methods. + @override + LinuxWebMessageChannel createPlatformWebMessageChannelStatic() { + return LinuxWebMessageChannel.static(); + } + + /// Creates a new [LinuxWebMessageChannel]. + @override + LinuxWebMessageChannel createPlatformWebMessageChannel( + PlatformWebMessageChannelCreationParams params, + ) { + return LinuxWebMessageChannel(params); + } + + /// Creates a new [LinuxWebMessagePort]. + @override + LinuxWebMessagePort createPlatformWebMessagePort( + PlatformWebMessagePortCreationParams params, + ) { + return LinuxWebMessagePort(params); + } + + /// Creates a new empty [PlatformWebMessageListener] to access static methods. + @override + PlatformWebMessageListener createPlatformWebMessageListenerStatic() { + return LinuxWebMessageListener.static(); + } + + /// Creates a new [LinuxWebMessageListener]. + @override + LinuxWebMessageListener createPlatformWebMessageListener( + PlatformWebMessageListenerCreationParams params, + ) { + return LinuxWebMessageListener(params); + } + + /// Creates a new [LinuxWebStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. + @override + LinuxWebStorage createPlatformWebStorage( + PlatformWebStorageCreationParams params, + ) { + return LinuxWebStorage(params); + } + + /// Creates a new empty [LinuxWebStorage] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. + @override + LinuxWebStorage createPlatformWebStorageStatic() { + return LinuxWebStorage( + LinuxWebStorageCreationParams( + localStorage: createPlatformLocalStorageStatic(), + sessionStorage: createPlatformSessionStorageStatic(), + ), + ); + } + + /// Creates a new [LinuxLocalStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. + @override + LinuxLocalStorage createPlatformLocalStorage( + PlatformLocalStorageCreationParams params, + ) { + return LinuxLocalStorage(params); + } + + /// Creates a new empty [LinuxLocalStorage] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. + @override + LinuxLocalStorage createPlatformLocalStorageStatic() { + return LinuxLocalStorage.defaultStorage(controller: null); + } + + /// Creates a new [LinuxSessionStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. + @override + LinuxSessionStorage createPlatformSessionStorage( + PlatformSessionStorageCreationParams params, + ) { + return LinuxSessionStorage(params); + } + + /// Creates a new empty [LinuxSessionStorage] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. + @override + LinuxSessionStorage createPlatformSessionStorageStatic() { + return LinuxSessionStorage.defaultStorage(controller: null); + } + + /// Creates a new empty [PlatformWebStorageManager] to access static methods. + @override + PlatformWebStorageManager createPlatformWebStorageManagerStatic() { + return LinuxWebStorageManager.static(); + } + + /// Creates a new [LinuxWebStorageManager]. + @override + LinuxWebStorageManager createPlatformWebStorageManager( + PlatformWebStorageManagerCreationParams params, + ) { + return LinuxWebStorageManager(params); + } + + /// Creates a new empty [PlatformAssetsPathHandler] to access static methods. + @override + PlatformAssetsPathHandler createPlatformAssetsPathHandlerStatic() { + return _PlatformAssetsPathHandler.static(); + } + + /// Creates a new empty [PlatformResourcesPathHandler] to access static methods. + @override + PlatformResourcesPathHandler createPlatformResourcesPathHandlerStatic() { + return _PlatformResourcesPathHandler.static(); + } + + /// Creates a new empty [PlatformInternalStoragePathHandler] to access static methods. + @override + PlatformInternalStoragePathHandler + createPlatformInternalStoragePathHandlerStatic() { + return _PlatformInternalStoragePathHandler.static(); + } + + /// Creates a new empty [PlatformCustomPathHandler] to access static methods. + @override + PlatformCustomPathHandler createPlatformCustomPathHandlerStatic() { + return _PlatformCustomPathHandler.static(); + } + + /// Creates a new [DefaultInAppLocalhostServer]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppLocalhostServer] in `flutter_inappwebview` instead. + @override + DefaultInAppLocalhostServer createPlatformInAppLocalhostServer( + PlatformInAppLocalhostServerCreationParams params, + ) { + return DefaultInAppLocalhostServer(params); + } + + /// Creates a new empty [DefaultInAppLocalhostServer] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppLocalhostServer] in `flutter_inappwebview` instead. + @override + DefaultInAppLocalhostServer createPlatformInAppLocalhostServerStatic() { + return DefaultInAppLocalhostServer.static(); + } + + /// Creates a new empty [PlatformWebViewFeature] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewFeature] in `flutter_inappwebview` instead + @override + PlatformWebViewFeature createPlatformWebViewFeatureStatic() { + return _PlatformWebViewFeature.static(); + } +} + +// Stub implementations for unsupported classes + +class _PlatformChromeSafariBrowser extends PlatformChromeSafariBrowser { + _PlatformChromeSafariBrowser(PlatformChromeSafariBrowserCreationParams params) + : super.implementation(params); + static final _PlatformChromeSafariBrowser _staticValue = + _PlatformChromeSafariBrowser( + const PlatformChromeSafariBrowserCreationParams(), + ); + + factory _PlatformChromeSafariBrowser.static() => _staticValue; +} + +class _PlatformProcessGlobalConfig extends PlatformProcessGlobalConfig { + _PlatformProcessGlobalConfig(PlatformProcessGlobalConfigCreationParams params) + : super.implementation(params); + static final _PlatformProcessGlobalConfig _staticValue = + _PlatformProcessGlobalConfig( + const PlatformProcessGlobalConfigCreationParams(), + ); + + factory _PlatformProcessGlobalConfig.static() => _staticValue; +} + +class _PlatformServiceWorkerController extends PlatformServiceWorkerController { + _PlatformServiceWorkerController( + PlatformServiceWorkerControllerCreationParams params, + ) : super.implementation(params); + static final _PlatformServiceWorkerController _staticValue = + _PlatformServiceWorkerController( + const PlatformServiceWorkerControllerCreationParams(), + ); + + factory _PlatformServiceWorkerController.static() => _staticValue; + + @override + ServiceWorkerClient? get serviceWorkerClient => throw UnimplementedError(); +} + +class _PlatformTracingController extends PlatformTracingController { + _PlatformTracingController(PlatformTracingControllerCreationParams params) + : super.implementation(params); + static final _PlatformTracingController _staticValue = + _PlatformTracingController( + const PlatformTracingControllerCreationParams(), + ); + + factory _PlatformTracingController.static() => _staticValue; +} + +class _PlatformPrintJobController extends PlatformPrintJobController { + _PlatformPrintJobController(PlatformPrintJobControllerCreationParams params) + : super.implementation(params); + + static final _PlatformPrintJobController _staticValue = + _PlatformPrintJobController( + const PlatformPrintJobControllerCreationParams(id: ''), + ); + + factory _PlatformPrintJobController.static() => _staticValue; +} + +class _PlatformPullToRefreshController extends PlatformPullToRefreshController { + _PlatformPullToRefreshController( + PlatformPullToRefreshControllerCreationParams params, + ) : super.implementation(params); + + static final _PlatformPullToRefreshController _staticValue = + _PlatformPullToRefreshController( + PlatformPullToRefreshControllerCreationParams(), + ); + + factory _PlatformPullToRefreshController.static() => _staticValue; +} + +class _PlatformWebAuthenticationSession + extends PlatformWebAuthenticationSession { + _PlatformWebAuthenticationSession( + PlatformWebAuthenticationSessionCreationParams params, + ) : super.implementation(params); + + static final _PlatformWebAuthenticationSession _staticValue = + _PlatformWebAuthenticationSession( + const PlatformWebAuthenticationSessionCreationParams(), + ); + + factory _PlatformWebAuthenticationSession.static() => _staticValue; +} + +class _PlatformWebNotificationController + extends PlatformWebNotificationController { + _PlatformWebNotificationController( + PlatformWebNotificationControllerCreationParams params, + ) : super.implementation(params); + + static final _PlatformWebNotificationController _staticValue = + _PlatformWebNotificationController( + PlatformWebNotificationControllerCreationParams( + id: '', + notification: WebNotification(), + ), + ); + + factory _PlatformWebNotificationController.static() => _staticValue; +} + +class _PlatformWebViewFeature extends PlatformWebViewFeature { + _PlatformWebViewFeature(PlatformWebViewFeatureCreationParams params) + : super.implementation(params); + + static final _PlatformWebViewFeature _staticValue = _PlatformWebViewFeature( + PlatformWebViewFeatureCreationParams(), + ); + factory _PlatformWebViewFeature.static() => _staticValue; +} + +class _PlatformAssetsPathHandler extends PlatformAssetsPathHandler { + _PlatformAssetsPathHandler(PlatformAssetsPathHandlerCreationParams params) + : super.implementation(params); + + static final _PlatformAssetsPathHandler _staticValue = + _PlatformAssetsPathHandler( + PlatformAssetsPathHandlerCreationParams( + PlatformPathHandlerCreationParams(path: ''), + ), + ); + + factory _PlatformAssetsPathHandler.static() => _staticValue; + + @override + PlatformPathHandlerEvents? eventHandler; + + @override + Map toMap({EnumMethod? enumMethod}) => { + "path": path, + "type": type, + }; + + @override + Map toJson() => toMap(); +} + +class _PlatformResourcesPathHandler extends PlatformResourcesPathHandler { + _PlatformResourcesPathHandler( + PlatformResourcesPathHandlerCreationParams params, + ) : super.implementation(params); + + static final _PlatformResourcesPathHandler _staticValue = + _PlatformResourcesPathHandler( + PlatformResourcesPathHandlerCreationParams( + PlatformPathHandlerCreationParams(path: ''), + ), + ); + + factory _PlatformResourcesPathHandler.static() => _staticValue; + + @override + PlatformPathHandlerEvents? eventHandler; + + @override + Map toMap({EnumMethod? enumMethod}) => { + "path": path, + "type": type, + }; + + @override + Map toJson() => toMap(); +} + +class _PlatformInternalStoragePathHandler + extends PlatformInternalStoragePathHandler { + _PlatformInternalStoragePathHandler( + PlatformInternalStoragePathHandlerCreationParams params, + ) : super.implementation(params); + + static final _PlatformInternalStoragePathHandler _staticValue = + _PlatformInternalStoragePathHandler( + PlatformInternalStoragePathHandlerCreationParams( + PlatformPathHandlerCreationParams(path: ''), + directory: '', + ), + ); + + factory _PlatformInternalStoragePathHandler.static() => _staticValue; + + @override + PlatformPathHandlerEvents? eventHandler; + + @override + Map toMap({EnumMethod? enumMethod}) => { + "path": path, + "type": type, + }; + + @override + Map toJson() => toMap(); +} + +class _PlatformCustomPathHandler extends PlatformCustomPathHandler { + _PlatformCustomPathHandler(PlatformCustomPathHandlerCreationParams params) + : super.implementation(params); + + static final _PlatformCustomPathHandler _staticValue = + _PlatformCustomPathHandler( + PlatformCustomPathHandlerCreationParams( + PlatformPathHandlerCreationParams(path: ''), + ), + ); + + factory _PlatformCustomPathHandler.static() => _staticValue; + + @override + PlatformPathHandlerEvents? eventHandler; + + @override + Map toMap({EnumMethod? enumMethod}) => { + "path": path, + "type": type, + }; + + @override + Map toJson() => toMap(); +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/main.dart new file mode 100644 index 00000000..3528f2fa --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/main.dart @@ -0,0 +1,10 @@ +export 'inappwebview_platform.dart'; +export 'cookie_manager/main.dart'; +export 'find_interaction/main.dart'; +export 'http_auth_credentials_database.dart'; +export 'in_app_browser/main.dart'; +export 'in_app_webview/main.dart'; +export 'proxy_controller/proxy_controller.dart'; +export 'web_message/main.dart'; +export 'web_storage/main.dart'; +export 'webview_environment/main.dart'; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/proxy_controller/proxy_controller.dart b/.vendor/flutter_inappwebview_linux/lib/src/proxy_controller/proxy_controller.dart new file mode 100644 index 00000000..3956cf06 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/proxy_controller/proxy_controller.dart @@ -0,0 +1,86 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxProxyController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformProxyControllerCreationParams] for +/// more information. +@immutable +class LinuxProxyControllerCreationParams + extends PlatformProxyControllerCreationParams { + /// Creates a new [LinuxProxyControllerCreationParams] instance. + const LinuxProxyControllerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformProxyControllerCreationParams params, + ) : super(); + + /// Creates a [LinuxProxyControllerCreationParams] instance based on [PlatformProxyControllerCreationParams]. + factory LinuxProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + PlatformProxyControllerCreationParams params, + ) { + return LinuxProxyControllerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformProxyController} +/// +/// Linux implementation of [PlatformProxyController] using WPE WebKit's +/// [WebKitNetworkProxySettings](https://wpewebkit.org/reference/stable/wpe-webkit-2.0/struct.NetworkProxySettings.html). +class LinuxProxyController extends PlatformProxyController { + static const MethodChannel _channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller', + ); + + /// Creates a new [LinuxProxyController]. + LinuxProxyController(PlatformProxyControllerCreationParams params) + : super.implementation( + params is LinuxProxyControllerCreationParams + ? params + : LinuxProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + params, + ), + ); + + static LinuxProxyController? _instance; + + /// Gets the [LinuxProxyController] shared instance. + static LinuxProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static LinuxProxyController _init() { + _instance = LinuxProxyController( + LinuxProxyControllerCreationParams( + const PlatformProxyControllerCreationParams(), + ), + ); + return _instance!; + } + + static final LinuxProxyController _staticValue = LinuxProxyController( + LinuxProxyControllerCreationParams( + const PlatformProxyControllerCreationParams(), + ), + ); + + /// Provide static access. + factory LinuxProxyController.static() { + return _staticValue; + } + + @override + Future setProxyOverride({required ProxySettings settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings.toMap()); + await _channel.invokeMethod('setProxyOverride', args); + } + + @override + Future clearProxyOverride() async { + Map args = {}; + await _channel.invokeMethod('clearProxyOverride', args); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_message/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_message/main.dart new file mode 100644 index 00000000..5be15cbd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_message/main.dart @@ -0,0 +1,3 @@ +export 'web_message_channel.dart' hide InternalWebMessageChannel; +export 'web_message_listener.dart'; +export 'web_message_port.dart' hide InternalWebMessagePort; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_channel.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_channel.dart new file mode 100644 index 00000000..dacfd1ba --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_channel.dart @@ -0,0 +1,130 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import 'web_message_port.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebMessageChannel]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessageChannelCreationParams] for +/// more information. +@immutable +class LinuxWebMessageChannelCreationParams + extends PlatformWebMessageChannelCreationParams { + /// Creates a new [LinuxWebMessageChannelCreationParams] instance. + const LinuxWebMessageChannelCreationParams({ + required super.id, + required super.port1, + required super.port2, + }); + + /// Creates a [LinuxWebMessageChannelCreationParams] instance based on [PlatformWebMessageChannelCreationParams]. + factory LinuxWebMessageChannelCreationParams.fromPlatformWebMessageChannelCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessageChannelCreationParams params, + ) { + return LinuxWebMessageChannelCreationParams( + id: params.id, + port1: params.port1, + port2: params.port2, + ); + } + + @override + String toString() { + return 'LinuxWebMessageChannelCreationParams{id: $id, port1: $port1, port2: $port2}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageChannel} +class LinuxWebMessageChannel extends PlatformWebMessageChannel + with ChannelController { + /// Constructs a [LinuxWebMessageChannel]. + LinuxWebMessageChannel(PlatformWebMessageChannelCreationParams params) + : super.implementation( + params is LinuxWebMessageChannelCreationParams + ? params + : LinuxWebMessageChannelCreationParams.fromPlatformWebMessageChannelCreationParams( + params, + ), + ) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_${params.id}', + ); + handler = _handleMethod; + initMethodCallHandler(); + } + + static final LinuxWebMessageChannel _staticValue = LinuxWebMessageChannel( + LinuxWebMessageChannelCreationParams( + id: '', + port1: LinuxWebMessagePort(LinuxWebMessagePortCreationParams(index: 0)), + port2: LinuxWebMessagePort(LinuxWebMessagePortCreationParams(index: 1)), + ), + ); + + /// Provide static access. + factory LinuxWebMessageChannel.static() { + return _staticValue; + } + + LinuxWebMessagePort get _linuxPort1 => port1 as LinuxWebMessagePort; + + LinuxWebMessagePort get _linuxPort2 => port2 as LinuxWebMessagePort; + + static LinuxWebMessageChannel? _fromMap(Map? map) { + if (map == null) { + return null; + } + var webMessageChannel = LinuxWebMessageChannel( + LinuxWebMessageChannelCreationParams( + id: map["id"], + port1: LinuxWebMessagePort(LinuxWebMessagePortCreationParams(index: 0)), + port2: LinuxWebMessagePort(LinuxWebMessagePortCreationParams(index: 1)), + ), + ); + webMessageChannel._linuxPort1.webMessageChannel = webMessageChannel; + webMessageChannel._linuxPort2.webMessageChannel = webMessageChannel; + return webMessageChannel; + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onMessage": + int index = call.arguments["index"]; + var port = index == 0 ? _linuxPort1 : _linuxPort2; + if (port.onMessage != null) { + WebMessage? message = call.arguments["message"] != null + ? WebMessage.fromMap( + call.arguments["message"].cast(), + ) + : null; + port.onMessage!(message); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + LinuxWebMessageChannel? fromMap(Map? map) { + return _fromMap(map); + } + + @override + void dispose() { + disposeChannel(); + } + + @override + String toString() { + return 'LinuxWebMessageChannel{id: $id, port1: $port1, port2: $port2}'; + } +} + +extension InternalWebMessageChannel on LinuxWebMessageChannel { + MethodChannel? get internalChannel => channel; +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_listener.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_listener.dart new file mode 100644 index 00000000..8806b7cf --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_listener.dart @@ -0,0 +1,186 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebMessageListener]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessageListenerCreationParams] for +/// more information. +@immutable +class LinuxWebMessageListenerCreationParams + extends PlatformWebMessageListenerCreationParams { + /// Creates a new [LinuxWebMessageListenerCreationParams] instance. + const LinuxWebMessageListenerCreationParams({ + required this.allowedOriginRules, + required super.jsObjectName, + super.onPostMessage, + }); + + /// Creates a [LinuxWebMessageListenerCreationParams] instance based on [PlatformWebMessageListenerCreationParams]. + factory LinuxWebMessageListenerCreationParams.fromPlatformWebMessageListenerCreationParams( + PlatformWebMessageListenerCreationParams params, + ) { + return LinuxWebMessageListenerCreationParams( + allowedOriginRules: params.allowedOriginRules ?? Set.from(["*"]), + jsObjectName: params.jsObjectName, + onPostMessage: params.onPostMessage, + ); + } + + @override + final Set allowedOriginRules; + + @override + String toString() { + return 'LinuxWebMessageListenerCreationParams{jsObjectName: $jsObjectName, allowedOriginRules: $allowedOriginRules, onPostMessage: $onPostMessage}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageListener} +class LinuxWebMessageListener extends PlatformWebMessageListener + with ChannelController { + /// Constructs a [LinuxWebMessageListener]. + LinuxWebMessageListener(PlatformWebMessageListenerCreationParams params) + : super.implementation( + params is LinuxWebMessageListenerCreationParams + ? params + : LinuxWebMessageListenerCreationParams.fromPlatformWebMessageListenerCreationParams( + params, + ), + ) { + assert( + !this._linuxParams.allowedOriginRules.contains(""), + "allowedOriginRules cannot contain empty strings", + ); + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${_id}_${params.jsObjectName}', + ); + handler = _handleMethod; + initMethodCallHandler(); + } + + static final LinuxWebMessageListener _staticValue = LinuxWebMessageListener( + LinuxWebMessageListenerCreationParams( + jsObjectName: '', + allowedOriginRules: Set.from(["*"]), + ), + ); + + /// Provide static access. + factory LinuxWebMessageListener.static() { + return _staticValue; + } + + ///Message Listener ID used internally. + final String _id = IdGenerator.generate(); + + LinuxJavaScriptReplyProxy? _replyProxy; + + LinuxWebMessageListenerCreationParams get _linuxParams => + params as LinuxWebMessageListenerCreationParams; + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onPostMessage": + if (_replyProxy == null) { + _replyProxy = LinuxJavaScriptReplyProxy( + PlatformJavaScriptReplyProxyCreationParams( + webMessageListener: this, + ), + ); + } + if (onPostMessage != null) { + WebMessage? message = call.arguments["message"] != null + ? WebMessage.fromMap( + call.arguments["message"].cast(), + ) + : null; + WebUri? sourceOrigin = call.arguments["sourceOrigin"] != null + ? WebUri(call.arguments["sourceOrigin"]) + : null; + bool isMainFrame = call.arguments["isMainFrame"]; + onPostMessage!(message, sourceOrigin, isMainFrame, _replyProxy!); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + void dispose() { + disposeChannel(); + } + + @override + Map toMap() { + return { + "id": _id, + "jsObjectName": params.jsObjectName, + "allowedOriginRules": _linuxParams.allowedOriginRules.toList(), + }; + } + + @override + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return 'LinuxWebMessageListener{id: ${_id}, jsObjectName: ${params.jsObjectName}, allowedOriginRules: ${params.allowedOriginRules}, replyProxy: $_replyProxy}'; + } +} + +/// Object specifying creation parameters for creating a [LinuxJavaScriptReplyProxy]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformJavaScriptReplyProxyCreationParams] for +/// more information. +@immutable +class LinuxJavaScriptReplyProxyCreationParams + extends PlatformJavaScriptReplyProxyCreationParams { + /// Creates a new [LinuxJavaScriptReplyProxyCreationParams] instance. + const LinuxJavaScriptReplyProxyCreationParams({ + required super.webMessageListener, + }); + + /// Creates a [LinuxJavaScriptReplyProxyCreationParams] instance based on [PlatformJavaScriptReplyProxyCreationParams]. + factory LinuxJavaScriptReplyProxyCreationParams.fromPlatformJavaScriptReplyProxyCreationParams( + PlatformJavaScriptReplyProxyCreationParams params, + ) { + return LinuxJavaScriptReplyProxyCreationParams( + webMessageListener: params.webMessageListener, + ); + } +} + +///{@macro flutter_inappwebview_platform_interface.JavaScriptReplyProxy} +class LinuxJavaScriptReplyProxy extends PlatformJavaScriptReplyProxy { + /// Constructs a [LinuxWebMessageListener]. + LinuxJavaScriptReplyProxy(PlatformJavaScriptReplyProxyCreationParams params) + : super.implementation( + params is LinuxJavaScriptReplyProxyCreationParams + ? params + : LinuxJavaScriptReplyProxyCreationParams.fromPlatformJavaScriptReplyProxyCreationParams( + params, + ), + ); + + LinuxWebMessageListener get _linuxWebMessageListener => + params.webMessageListener as LinuxWebMessageListener; + + @override + Future postMessage(WebMessage message) async { + Map args = {}; + args.putIfAbsent('message', () => message.toMap()); + await _linuxWebMessageListener.channel?.invokeMethod('postMessage', args); + } + + @override + String toString() { + return 'LinuxJavaScriptReplyProxy{}'; + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_port.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_port.dart new file mode 100644 index 00000000..ebdad3e4 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_message/web_message_port.dart @@ -0,0 +1,99 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'web_message_channel.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebMessagePort]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessagePortCreationParams] for +/// more information. +@immutable +class LinuxWebMessagePortCreationParams + extends PlatformWebMessagePortCreationParams { + /// Creates a new [LinuxWebMessagePortCreationParams] instance. + const LinuxWebMessagePortCreationParams({required super.index}); + + /// Creates a [LinuxWebMessagePortCreationParams] instance based on [PlatformWebMessagePortCreationParams]. + factory LinuxWebMessagePortCreationParams.fromPlatformWebMessagePortCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessagePortCreationParams params, + ) { + return LinuxWebMessagePortCreationParams(index: params.index); + } + + @override + String toString() { + return 'LinuxWebMessagePortCreationParams{index: $index}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort} +class LinuxWebMessagePort extends PlatformWebMessagePort { + WebMessageCallback? _onMessage; + late LinuxWebMessageChannel _webMessageChannel; + + /// Constructs a [LinuxWebMessagePort]. + LinuxWebMessagePort(PlatformWebMessagePortCreationParams params) + : super.implementation( + params is LinuxWebMessagePortCreationParams + ? params + : LinuxWebMessagePortCreationParams.fromPlatformWebMessagePortCreationParams( + params, + ), + ); + + @override + Future setWebMessageCallback(WebMessageCallback? onMessage) async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + await _webMessageChannel.internalChannel?.invokeMethod( + 'setWebMessageCallback', + args, + ); + _onMessage = onMessage; + } + + @override + Future postMessage(WebMessage message) async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + args.putIfAbsent('message', () => message.toMap()); + await _webMessageChannel.internalChannel?.invokeMethod('postMessage', args); + } + + @override + Future close() async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + await _webMessageChannel.internalChannel?.invokeMethod('close', args); + } + + @override + Map toMap({EnumMethod? enumMethod}) { + return { + "index": params.index, + "webMessageChannelId": _webMessageChannel.params.id, + }; + } + + @override + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'LinuxWebMessagePort{index: ${params.index}}'; + } +} + +extension InternalWebMessagePort on LinuxWebMessagePort { + WebMessageCallback? get onMessage => _onMessage; + set onMessage(WebMessageCallback? value) => _onMessage = value; + + LinuxWebMessageChannel get webMessageChannel => _webMessageChannel; + set webMessageChannel(LinuxWebMessageChannel value) => + _webMessageChannel = value; +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_storage/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/main.dart new file mode 100644 index 00000000..dab327ba --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/main.dart @@ -0,0 +1,2 @@ +export 'web_storage.dart'; +export 'web_storage_manager.dart'; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage.dart new file mode 100644 index 00000000..d44d072a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage.dart @@ -0,0 +1,305 @@ +import 'dart:convert'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../in_app_webview/in_app_webview_controller.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebStorageCreationParams] for +/// more information. +class LinuxWebStorageCreationParams extends PlatformWebStorageCreationParams { + /// Creates a new [LinuxWebStorageCreationParams] instance. + LinuxWebStorageCreationParams({ + required super.localStorage, + required super.sessionStorage, + }); + + /// Creates a [LinuxWebStorageCreationParams] instance based on [PlatformWebStorageCreationParams]. + factory LinuxWebStorageCreationParams.fromPlatformWebStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebStorageCreationParams params, + ) { + return LinuxWebStorageCreationParams( + localStorage: params.localStorage, + sessionStorage: params.sessionStorage, + ); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebStorage} +class LinuxWebStorage extends PlatformWebStorage { + /// Constructs a [LinuxWebStorage]. + LinuxWebStorage(PlatformWebStorageCreationParams params) + : super.implementation( + params is LinuxWebStorageCreationParams + ? params + : LinuxWebStorageCreationParams.fromPlatformWebStorageCreationParams( + params, + ), + ); + + @override + PlatformLocalStorage get localStorage => params.localStorage; + + @override + PlatformSessionStorage get sessionStorage => params.sessionStorage; + + @override + void dispose() { + localStorage.dispose(); + sessionStorage.dispose(); + } +} + +/// Object specifying creation parameters for creating a [LinuxStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformStorageCreationParams] for +/// more information. +class LinuxStorageCreationParams extends PlatformStorageCreationParams { + /// Creates a new [LinuxStorageCreationParams] instance. + LinuxStorageCreationParams({ + required super.controller, + required super.webStorageType, + }); + + /// Creates a [LinuxStorageCreationParams] instance based on [PlatformStorageCreationParams]. + factory LinuxStorageCreationParams.fromPlatformStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformStorageCreationParams params, + ) { + return LinuxStorageCreationParams( + controller: params.controller, + webStorageType: params.webStorageType, + ); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformStorage} +abstract mixin class LinuxStorage implements PlatformStorage { + @override + LinuxInAppWebViewController? controller; + + @override + Future length() async { + var result = await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.length; + """, + ); + return result != null ? int.parse(json.decode(result)) : null; + } + + @override + Future setItem({required String key, required dynamic value}) async { + var encodedValue = json.encode(value); + await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"}); + """, + ); + } + + @override + Future getItem({required String key}) async { + var itemValue = await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.getItem("$key"); + """, + ); + + if (itemValue == null) { + return null; + } + + try { + return json.decode(itemValue); + } catch (e) {} + + return itemValue; + } + + @override + Future removeItem({required String key}) async { + await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.removeItem("$key"); + """, + ); + } + + @override + Future> getItems() async { + var webStorageItems = []; + + List>? items = (await controller?.evaluateJavascript( + source: + """ +(function() { + var webStorageItems = []; + for(var i = 0; i < window.$webStorageType.length; i++){ + var key = window.$webStorageType.key(i); + webStorageItems.push( + { + key: key, + value: window.$webStorageType.getItem(key) + } + ); + } + return webStorageItems; +})(); + """, + ))?.cast>(); + + if (items == null) { + return webStorageItems; + } + + for (var item in items) { + webStorageItems.add( + WebStorageItem(key: item["key"], value: item["value"]), + ); + } + + return webStorageItems; + } + + @override + Future clear() async { + await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.clear(); + """, + ); + } + + @override + Future key({required int index}) async { + var result = await controller?.evaluateJavascript( + source: + """ + window.$webStorageType.key($index); + """, + ); + return result != null ? json.decode(result) : null; + } + + @override + void dispose() { + controller = null; + } +} + +/// Object specifying creation parameters for creating a [LinuxLocalStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformLocalStorageCreationParams] for +/// more information. +class LinuxLocalStorageCreationParams extends PlatformLocalStorageCreationParams { + /// Creates a new [LinuxLocalStorageCreationParams] instance. + LinuxLocalStorageCreationParams(super.params); + + /// Creates a [LinuxLocalStorageCreationParams] instance based on [PlatformLocalStorageCreationParams]. + factory LinuxLocalStorageCreationParams.fromPlatformLocalStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformLocalStorageCreationParams params, + ) { + return LinuxLocalStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformLocalStorage} +class LinuxLocalStorage extends PlatformLocalStorage with LinuxStorage { + /// Constructs a [LinuxLocalStorage]. + LinuxLocalStorage(PlatformLocalStorageCreationParams params) + : super.implementation( + params is LinuxLocalStorageCreationParams + ? params + : LinuxLocalStorageCreationParams.fromPlatformLocalStorageCreationParams( + params, + ), + ); + + /// Default storage + factory LinuxLocalStorage.defaultStorage({ + required PlatformInAppWebViewController? controller, + }) { + return LinuxLocalStorage( + LinuxLocalStorageCreationParams( + PlatformLocalStorageCreationParams( + PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.LOCAL_STORAGE, + ), + ), + ), + ); + } + + @override + LinuxInAppWebViewController? get controller => + params.controller as LinuxInAppWebViewController?; +} + +/// Object specifying creation parameters for creating a [LinuxSessionStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformSessionStorageCreationParams] for +/// more information. +class LinuxSessionStorageCreationParams + extends PlatformSessionStorageCreationParams { + /// Creates a new [LinuxSessionStorageCreationParams] instance. + LinuxSessionStorageCreationParams(super.params); + + /// Creates a [LinuxSessionStorageCreationParams] instance based on [PlatformSessionStorageCreationParams]. + factory LinuxSessionStorageCreationParams.fromPlatformSessionStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformSessionStorageCreationParams params, + ) { + return LinuxSessionStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformSessionStorage} +class LinuxSessionStorage extends PlatformSessionStorage with LinuxStorage { + /// Constructs a [LinuxSessionStorage]. + LinuxSessionStorage(PlatformSessionStorageCreationParams params) + : super.implementation( + params is LinuxSessionStorageCreationParams + ? params + : LinuxSessionStorageCreationParams.fromPlatformSessionStorageCreationParams( + params, + ), + ); + + /// Default storage + factory LinuxSessionStorage.defaultStorage({ + required PlatformInAppWebViewController? controller, + }) { + return LinuxSessionStorage( + LinuxSessionStorageCreationParams( + PlatformSessionStorageCreationParams( + PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.SESSION_STORAGE, + ), + ), + ), + ); + } + + @override + LinuxInAppWebViewController? get controller => + params.controller as LinuxInAppWebViewController?; +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage_manager.dart b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage_manager.dart new file mode 100644 index 00000000..80b41c09 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/web_storage/web_storage_manager.dart @@ -0,0 +1,183 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebStorageManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebStorageManagerCreationParams] for +/// more information. +class LinuxWebStorageManagerCreationParams + extends PlatformWebStorageManagerCreationParams { + /// Creates a new [LinuxWebStorageManagerCreationParams] instance. + const LinuxWebStorageManagerCreationParams(); + + /// Creates a [LinuxWebStorageManagerCreationParams] instance based on [PlatformWebStorageManagerCreationParams]. + factory LinuxWebStorageManagerCreationParams.fromPlatformWebStorageManagerCreationParams( + PlatformWebStorageManagerCreationParams params, + ) { + return const LinuxWebStorageManagerCreationParams(); + } +} + +/// Implementation of [PlatformWebStorageManager] for Linux using WPE WebKit. +/// +/// Uses [WebKitWebsiteDataManager](https://wpewebkit.org/reference/stable/wpe-webkit-2.0/class.WebsiteDataManager.html) +/// to manage website data. +class LinuxWebStorageManager extends PlatformWebStorageManager { + static const MethodChannel _channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_webstoragemanager', + ); + + /// Constructs a [LinuxWebStorageManager]. + LinuxWebStorageManager(PlatformWebStorageManagerCreationParams params) + : super.implementation( + params is LinuxWebStorageManagerCreationParams + ? params + : LinuxWebStorageManagerCreationParams.fromPlatformWebStorageManagerCreationParams( + params, + ), + ); + + static final LinuxWebStorageManager _instance = LinuxWebStorageManager( + const LinuxWebStorageManagerCreationParams(), + ); + + /// The [LinuxWebStorageManager] singleton instance. + static LinuxWebStorageManager instance() => _instance; + + /// Creates and returns a new [LinuxWebStorageManager] for static methods. + factory LinuxWebStorageManager.static() => _instance; + + /// Maps WebsiteDataType to WPE WebKit data type strings. + static String _toWpeDataType(WebsiteDataType type) { + if (type == WebsiteDataType.WKWebsiteDataTypeDiskCache) { + return 'WEBKIT_WEBSITE_DATA_DISK_CACHE'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeMemoryCache) { + return 'WEBKIT_WEBSITE_DATA_MEMORY_CACHE'; + } else if (type == + WebsiteDataType.WKWebsiteDataTypeOfflineWebApplicationCache) { + return 'WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeCookies) { + return 'WEBKIT_WEBSITE_DATA_COOKIES'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeSessionStorage) { + return 'WEBKIT_WEBSITE_DATA_SESSION_STORAGE'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeLocalStorage) { + return 'WEBKIT_WEBSITE_DATA_LOCAL_STORAGE'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeIndexedDBDatabases) { + return 'WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES'; + } else if (type == + WebsiteDataType.WKWebsiteDataTypeServiceWorkerRegistrations) { + return 'WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeFetchCache) { + // WPE uses same as disk cache for fetch cache + return 'WEBKIT_WEBSITE_DATA_DISK_CACHE'; + } else if (type == WebsiteDataType.WKWebsiteDataTypeWebSQLDatabases) { + // WebSQL is deprecated, map to local storage + return 'WEBKIT_WEBSITE_DATA_LOCAL_STORAGE'; + } + // Default fallback + return 'WEBKIT_WEBSITE_DATA_LOCAL_STORAGE'; + } + + /// Maps WPE WebKit data type string to WebsiteDataType. + static WebsiteDataType? _fromWpeDataType(String wpeType) { + if (wpeType == 'WEBKIT_WEBSITE_DATA_DISK_CACHE') { + return WebsiteDataType.WKWebsiteDataTypeDiskCache; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_MEMORY_CACHE') { + return WebsiteDataType.WKWebsiteDataTypeMemoryCache; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE') { + return WebsiteDataType.WKWebsiteDataTypeOfflineWebApplicationCache; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_COOKIES') { + return WebsiteDataType.WKWebsiteDataTypeCookies; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_SESSION_STORAGE') { + return WebsiteDataType.WKWebsiteDataTypeSessionStorage; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_LOCAL_STORAGE') { + return WebsiteDataType.WKWebsiteDataTypeLocalStorage; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES') { + return WebsiteDataType.WKWebsiteDataTypeIndexedDBDatabases; + } else if (wpeType == 'WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS') { + return WebsiteDataType.WKWebsiteDataTypeServiceWorkerRegistrations; + } + return null; + } + + @override + Future> fetchDataRecords({ + required Set dataTypes, + }) async { + final List wpeDataTypes = dataTypes + .map((type) => _toWpeDataType(type)) + .toList(); + + final result = await _channel.invokeMethod>( + 'fetchDataRecords', + {'dataTypes': wpeDataTypes}, + ); + + if (result == null) { + return []; + } + + return result.cast>().map((recordMap) { + final String? displayName = recordMap['displayName'] as String?; + final List? dataTypesRaw = + recordMap['dataTypes'] as List?; + + Set? dataTypesSet; + if (dataTypesRaw != null) { + dataTypesSet = dataTypesRaw + .cast() + .map((typeStr) => _fromWpeDataType(typeStr)) + .whereType() + .toSet(); + } + + return WebsiteDataRecord( + displayName: displayName, + dataTypes: dataTypesSet, + ); + }).toList(); + } + + @override + Future removeDataFor({ + required Set dataTypes, + required List dataRecords, + }) async { + final List wpeDataTypes = dataTypes + .map((type) => _toWpeDataType(type)) + .toList(); + + final List> recordList = dataRecords.map((record) { + return { + 'displayName': record.displayName, + 'dataTypes': record.dataTypes + ?.map((type) => _toWpeDataType(type)) + .toList(), + }; + }).toList(); + + await _channel.invokeMethod('removeDataFor', { + 'dataTypes': wpeDataTypes, + 'recordList': recordList, + }); + } + + @override + Future removeDataModifiedSince({ + required Set dataTypes, + required DateTime date, + }) async { + final List wpeDataTypes = dataTypes + .map((type) => _toWpeDataType(type)) + .toList(); + + // Convert DateTime to Unix timestamp (seconds since epoch) + final int timestamp = date.millisecondsSinceEpoch ~/ 1000; + + await _channel.invokeMethod('removeDataModifiedSince', { + 'dataTypes': wpeDataTypes, + 'timestamp': timestamp, + }); + } +} diff --git a/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/main.dart b/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/main.dart new file mode 100644 index 00000000..f9bf33a7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/main.dart @@ -0,0 +1 @@ +export 'webview_environment.dart'; diff --git a/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/webview_environment.dart b/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/webview_environment.dart new file mode 100644 index 00000000..fd95f3e0 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/lib/src/webview_environment/webview_environment.dart @@ -0,0 +1,158 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [LinuxWebViewEnvironment]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +@immutable +class LinuxWebViewEnvironmentCreationParams + extends PlatformWebViewEnvironmentCreationParams { + /// Creates a new [LinuxWebViewEnvironmentCreationParams] instance. + const LinuxWebViewEnvironmentCreationParams({super.settings}); + + /// Creates a [LinuxWebViewEnvironmentCreationParams] instance based on [PlatformWebViewEnvironmentCreationParams]. + factory LinuxWebViewEnvironmentCreationParams.fromPlatformWebViewEnvironmentCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewEnvironmentCreationParams params, + ) { + return LinuxWebViewEnvironmentCreationParams(settings: params.settings); + } +} + +/// Linux implementation of [PlatformWebViewEnvironment]. +/// +/// This class provides access to WPE WebKit version information and +/// WebKitWebContext management on Linux. +/// +///**Officially Supported Platforms/Implementations**: +///- Linux +class LinuxWebViewEnvironment extends PlatformWebViewEnvironment + with ChannelController { + /// Static method channel for WebViewEnvironment operations. + static final MethodChannel _staticChannel = MethodChannel( + 'com.pichillilorenzo/flutter_webview_environment', + ); + + @override + final String id = IdGenerator.generate(); + + /// Creates a new [LinuxWebViewEnvironment]. + LinuxWebViewEnvironment(PlatformWebViewEnvironmentCreationParams params) + : super.implementation( + params is LinuxWebViewEnvironmentCreationParams + ? params + : LinuxWebViewEnvironmentCreationParams.fromPlatformWebViewEnvironmentCreationParams( + params, + ), + ); + + /// Static instance for accessing static methods. + static final LinuxWebViewEnvironment _staticValue = LinuxWebViewEnvironment( + LinuxWebViewEnvironmentCreationParams(), + ); + + /// Factory constructor for accessing static methods. + factory LinuxWebViewEnvironment.static() { + return _staticValue; + } + + _debugLog(String method, dynamic args) { + debugLog( + className: runtimeType.toString(), + id: id, + debugLoggingSettings: PlatformWebViewEnvironment.debugLoggingSettings, + method: method, + args: args, + ); + } + + Future _handleMethod(MethodCall call) async { + if (PlatformWebViewEnvironment.debugLoggingSettings.enabled) { + _debugLog(call.method, call.arguments); + } + + switch (call.method) { + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} + /// + /// On Linux, this creates a new WebKitWebContext instance. + @override + Future create({ + WebViewEnvironmentSettings? settings, + }) async { + final env = LinuxWebViewEnvironment( + LinuxWebViewEnvironmentCreationParams(settings: settings), + ); + + Map args = {}; + args.putIfAbsent('id', () => env.id); + if (settings != null) { + args.putIfAbsent('settings', () => settings.toMap()); + } + await _staticChannel.invokeMethod('create', args); + + env.channel = MethodChannel( + 'com.pichillilorenzo/flutter_webview_environment_${env.id}', + ); + env.handler = env.handleMethod; + env.initMethodCallHandler(); + return env; + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + /// + /// On Linux, this returns the WPE WebKit version (e.g., "2.42.0"). + /// + /// The [browserExecutableFolder] parameter is ignored on Linux as WPE WebKit + /// is a system library. + @override + Future getAvailableVersion({String? browserExecutableFolder}) async { + return await _staticChannel.invokeMethod('getAvailableVersion'); + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isSpellCheckingEnabled} + @override + Future isSpellCheckingEnabled() async { + return await channel?.invokeMethod('isSpellCheckingEnabled') ?? false; + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getSpellCheckingLanguages} + @override + Future> getSpellCheckingLanguages() async { + final result = await channel?.invokeMethod( + 'getSpellCheckingLanguages', + ); + return result?.cast() ?? []; + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getCacheModel} + @override + Future getCacheModel() async { + final result = await channel?.invokeMethod('getCacheModel'); + return CacheModel.fromNativeValue(result); + } + + /// {@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isAutomationAllowed} + @override + Future isAutomationAllowed() async { + return await channel?.invokeMethod('isAutomationAllowed') ?? false; + } + + @override + Future dispose() async { + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + } +} + +extension InternalLinuxWebViewEnvironment on LinuxWebViewEnvironment { + get handleMethod => _handleMethod; +} diff --git a/.vendor/flutter_inappwebview_linux/linux/CMakeLists.txt b/.vendor/flutter_inappwebview_linux/linux/CMakeLists.txt new file mode 100644 index 00000000..27f93348 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/CMakeLists.txt @@ -0,0 +1,430 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "flutter_inappwebview_linux") +project(${PROJECT_NAME} LANGUAGES CXX C) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "flutter_inappwebview_linux_plugin") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# nlohmann/json version for JSON parsing +set(NLOHMANN_JSON_VERSION "3.11.3") + +# === WPE WebKit Backend === +# This plugin uses WPE WebKit for offscreen web rendering. +# WPE WebKit is designed for embedded systems and provides excellent offscreen +# rendering performance through DMA-BUF buffer sharing. +# +# Dual API Support: +# - WPEPlatform API (wpe-platform-2.0): NEW default API for WPE WebKit 2.40+ +# - WPEBackend-FDO (wpebackend-fdo-1.0): Legacy fallback for older systems +# +# Required packages: +# - wpe-webkit-2.0 (or wpe-webkit-1.1 on older systems) +# - wpe-platform-2.0 (preferred) OR wpebackend-fdo-1.0 (fallback) +# - libwpe-1.0 +# +# See WPE_BACKEND.md for installation instructions. + +find_package(PkgConfig REQUIRED) + +# Always require epoxy for OpenGL support +pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy) + +# GTK is still needed for GDK (display access, event handling) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +message(STATUS "flutter_inappwebview_linux: Using WPE WebKit backend") + +# Try wpe-webkit-2.0 first (newer API), fall back to wpe-webkit-1.1, then wpe-webkit-1.0 +pkg_check_modules(WPE_WEBKIT QUIET IMPORTED_TARGET wpe-webkit-2.0) +if(NOT WPE_WEBKIT_FOUND) + pkg_check_modules(WPE_WEBKIT QUIET IMPORTED_TARGET wpe-webkit-1.1) + if(WPE_WEBKIT_FOUND) + message(STATUS "flutter_inappwebview_linux: Found wpe-webkit-1.1 (${WPE_WEBKIT_VERSION})") + else() + pkg_check_modules(WPE_WEBKIT QUIET IMPORTED_TARGET wpe-webkit-1.0) + if(WPE_WEBKIT_FOUND) + message(STATUS "flutter_inappwebview_linux: Found wpe-webkit-1.0 (${WPE_WEBKIT_VERSION})") + endif() + endif() +else() + message(STATUS "flutter_inappwebview_linux: Found wpe-webkit-2.0 (${WPE_WEBKIT_VERSION})") +endif() + +if(NOT WPE_WEBKIT_FOUND) + message(FATAL_ERROR "WPE WebKit not found. Please install libwpewebkit-1.0-dev (Ubuntu/Debian) or wpe-webkit package.\n" + "See WPE_BACKEND.md or https://wpewebkit.org/about/get-wpe.html") +endif() + +# === WPE Platform API Detection (NEW - default) === +# Check for WPEPlatform API first (wpe-platform-2.0) - this is the modern API +# introduced in WPE WebKit 2.40+ and is the preferred approach. +pkg_check_modules(WPE_PLATFORM QUIET IMPORTED_TARGET wpe-platform-2.0) +pkg_check_modules(WPE_PLATFORM_HEADLESS QUIET IMPORTED_TARGET wpe-platform-headless-2.0) + +if(WPE_PLATFORM_FOUND AND WPE_PLATFORM_HEADLESS_FOUND) + # Additional check: verify that WebKit was actually built WITH WPEPlatform support + # The pkg-config file may exist but WebKit headers may not have the required functions + # This happens when WPE WebKit is rebuilt without WPEPlatform support + include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_INCLUDES ${WPE_WEBKIT_INCLUDE_DIRS} ${WPE_PLATFORM_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${WPE_WEBKIT_LIBRARIES} ${WPE_PLATFORM_LIBRARIES}) + check_cxx_source_compiles(" + #include + int main() { + WebKitWebView* view = nullptr; + webkit_web_view_get_wpe_view(view); + return 0; + } + " WEBKIT_HAS_WPE_PLATFORM_API) + + if(WEBKIT_HAS_WPE_PLATFORM_API) + message(STATUS "flutter_inappwebview_linux: Found wpe-platform-2.0 (WPEPlatform API - DEFAULT)") + message(STATUS "flutter_inappwebview_linux: Found wpe-platform-headless-2.0") + message(STATUS "flutter_inappwebview_linux: WebKit has WPEPlatform API support (webkit_web_view_get_wpe_view)") + set(HAVE_WPE_PLATFORM ON) + add_compile_definitions(HAVE_WPE_PLATFORM=1) + else() + message(STATUS "flutter_inappwebview_linux: wpe-platform-2.0 pkg-config found, but WebKit lacks WPEPlatform API") + message(STATUS "flutter_inappwebview_linux: WebKit was likely built without WPEPlatform support, falling back to legacy FDO") + endif() +endif() + +# === WPE Backend FDO Detection (Legacy - fallback) === +# Check for WPEBackend-FDO as fallback ONLY if WPEPlatform is NOT available +# WPEPlatform and WPEBackend-FDO are mutually exclusive at compile time +pkg_check_modules(WPE_FDO QUIET IMPORTED_TARGET wpebackend-fdo-1.0) +if(WPE_FDO_FOUND AND NOT HAVE_WPE_PLATFORM) + message(STATUS "flutter_inappwebview_linux: Found wpebackend-fdo-1.0 (Legacy FDO API)") + set(HAVE_WPE_FDO ON) + add_compile_definitions(HAVE_WPE_BACKEND_LEGACY=1) +elseif(WPE_FDO_FOUND AND HAVE_WPE_PLATFORM) + message(STATUS "flutter_inappwebview_linux: wpebackend-fdo-1.0 available but not used (WPEPlatform preferred)") +endif() + +# Require at least one backend +if(NOT HAVE_WPE_PLATFORM AND NOT HAVE_WPE_FDO) + message(FATAL_ERROR "Neither WPEPlatform (wpe-platform-2.0) nor WPEBackend-FDO (wpebackend-fdo-1.0) found.\n" + "Please install one of:\n" + " - WPE WebKit 2.40+ with WPEPlatform support (recommended)\n" + " - wpebackend-fdo-1.0-dev (legacy fallback)\n" + "See WPE_BACKEND.md for installation instructions.") +endif() + +# Check for libwpe +pkg_check_modules(LIBWPE QUIET IMPORTED_TARGET wpe-1.0) +if(NOT LIBWPE_FOUND) + message(FATAL_ERROR "libwpe (wpe-1.0) not found. Please install libwpe-1.0-dev.") +endif() + +# Check for libsecret for secure credential storage +pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1) +message(STATUS "flutter_inappwebview_linux: Found libsecret-1 (${LIBSECRET_VERSION})") + +# Find wayland-server for SHM buffer handling +pkg_check_modules(WAYLAND_SERVER REQUIRED IMPORTED_TARGET wayland-server) + +# Enable SIMD optimizations for color conversion +# These flags enable NEON on ARM64 and SSE/SSSE3 on x86_64 +include(CheckCXXCompilerFlag) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") + # ARM64: NEON is always available + message(STATUS "flutter_inappwebview_linux: ARM64 detected, NEON enabled") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64") + # x86_64: Check for SSSE3 support + check_cxx_compiler_flag("-mssse3" COMPILER_SUPPORTS_SSSE3) + if(COMPILER_SUPPORTS_SSSE3) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mssse3") + message(STATUS "flutter_inappwebview_linux: x86_64 with SSSE3 enabled") + else() + check_cxx_compiler_flag("-msse2" COMPILER_SUPPORTS_SSE2) + if(COMPILER_SUPPORTS_SSE2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") + message(STATUS "flutter_inappwebview_linux: x86_64 with SSE2 enabled") + endif() + endif() +endif() + +# === nlohmann/json dependency for JSON parsing === +include(FetchContent) +FetchContent_Declare( + nlohmann_json + URL https://github.com/nlohmann/json/releases/download/v${NLOHMANN_JSON_VERSION}/json.tar.xz + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +# Don't build tests/examples +set(JSON_BuildTests OFF CACHE INTERNAL "") +FetchContent_MakeAvailable(nlohmann_json) +# Suppress deprecated literal operator warnings from nlohmann_json +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-literal-operator") +message(STATUS "flutter_inappwebview_linux: nlohmann/json ${NLOHMANN_JSON_VERSION} configured") + +# Plugin source files +list(APPEND PLUGIN_SOURCES + "flutter_inappwebview_linux_plugin.cc" + "plugin_instance.cc" + "cookie_manager.cc" + "credential_database.cc" + "proxy_manager.cc" + "utils/software_rendering.cc" + "web_storage_manager.cc" + "webview_environment.cc" + "content_blocker/content_blocker_handler.cc" + "find_interaction/find_interaction_controller.cc" + "find_interaction/find_interaction_channel_delegate.cc" + "headless_in_app_webview/headless_in_app_webview.cc" + "headless_in_app_webview/headless_in_app_webview_manager.cc" + "headless_in_app_webview/headless_webview_channel_delegate.cc" + "in_app_browser/in_app_browser.cc" + "in_app_browser/in_app_browser_channel_delegate.cc" + "in_app_browser/in_app_browser_manager.cc" + "in_app_browser/in_app_browser_settings.cc" + "in_app_webview/in_app_webview_manager.cc" + "in_app_webview/custom_platform_view.cc" + "in_app_webview/inappwebview_texture.cc" + "in_app_webview/inappwebview_egl_texture.cc" + "in_app_webview/in_app_webview.cc" + "in_app_webview/in_app_webview_settings.cc" + "in_app_webview/user_content_controller.cc" + "in_app_webview/webview_channel_delegate.cc" + "types/channel_delegate.cc" + "types/client_cert_challenge.cc" + "types/client_cert_response.cc" + "types/content_world.cc" + "types/context_menu_popup.cc" + "types/create_window_action.cc" + "types/custom_scheme_response.cc" + "types/download_start_request.cc" + "types/download_start_response.cc" + "types/find_session.cc" + "types/http_auth_response.cc" + "types/http_authentication_challenge.cc" + "types/javascript_handler_function_data.cc" + "types/js_alert_request.cc" + "types/js_alert_response.cc" + "types/js_before_unload_response.cc" + "types/js_confirm_request.cc" + "types/js_confirm_response.cc" + "types/js_prompt_request.cc" + "types/js_prompt_response.cc" + "types/hit_test_result.cc" + "types/navigation_action.cc" + "types/option_menu_popup.cc" + "types/permission_request.cc" + "types/permission_response.cc" + "types/plugin_script.cc" + "types/server_trust_auth_response.cc" + "types/server_trust_challenge.cc" + "types/show_file_chooser_response.cc" + "types/ssl_certificate.cc" + "types/url_credential.cc" + "types/url_protection_space.cc" + "types/url_request.cc" + "types/user_script.cc" + "types/web_resource_error.cc" + "types/web_resource_request.cc" + "types/web_resource_response.cc" + "types/web_view_transport.cc" + "web_message/web_message_channel.cc" + "web_message/web_message_listener.cc" + "web_message/web_message_listener_channel_delegate.cc" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Set RPATH so the plugin loads bundled WPE libraries from the same directory. +# $ORIGIN refers to the directory containing the plugin shared library. +# This eliminates the need for LD_LIBRARY_PATH or launcher scripts. +set_target_properties(${PLUGIN_NAME} PROPERTIES + INSTALL_RPATH "$ORIGIN" + BUILD_RPATH "$ORIGIN" + BUILD_WITH_INSTALL_RPATH TRUE) + +# Source include directories and library dependencies +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_include_directories(${PLUGIN_NAME} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + ${EPOXY_INCLUDE_DIRS} + ${WPE_WEBKIT_INCLUDE_DIRS}) + +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::EPOXY) +target_link_libraries(${PLUGIN_NAME} PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WPE_WEBKIT) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::LIBWPE) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WAYLAND_SERVER) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::LIBSECRET) + +# Link WPEPlatform if available (new API - default) +if(HAVE_WPE_PLATFORM) + target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WPE_PLATFORM) + target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WPE_PLATFORM_HEADLESS) +endif() + +# Link WPEBackend-FDO if available (legacy fallback) +if(HAVE_WPE_FDO) + target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WPE_FDO) +endif() + +# === Library Bundling === +# Collect WPE libraries to bundle with the application +# This ensures the app can run without setting LD_LIBRARY_PATH + +set(WPE_BUNDLED_LIBS "") + +# Helper function to find and add real shared library files (resolving symlinks) +function(find_and_add_library LIB_NAME SEARCH_PATHS OUT_LIST) + foreach(SEARCH_PATH ${SEARCH_PATHS}) + # Look for the versioned .so file (e.g., libwpe-1.0.so.1.10.0) + file(GLOB LIB_FILES "${SEARCH_PATH}/${LIB_NAME}.so.*.*.*") + if(NOT LIB_FILES) + # Try less specific pattern (e.g., libwpe-1.0.so.1) + file(GLOB LIB_FILES "${SEARCH_PATH}/${LIB_NAME}.so.*") + endif() + if(LIB_FILES) + # Get the first matching file + list(GET LIB_FILES 0 LIB_FILE) + # Resolve symlinks to get the real file + get_filename_component(REAL_LIB "${LIB_FILE}" REALPATH) + if(EXISTS "${REAL_LIB}") + list(APPEND ${OUT_LIST} "${REAL_LIB}") + set(${OUT_LIST} ${${OUT_LIST}} PARENT_SCOPE) + message(STATUS "flutter_inappwebview_linux: Will bundle ${REAL_LIB}") + return() + endif() + endif() + endforeach() + message(WARNING "flutter_inappwebview_linux: Could not find ${LIB_NAME} to bundle") +endfunction() + +# Get library directories from pkg-config +set(WPE_LIB_DIRS "") +if(WPE_WEBKIT_LIBRARY_DIRS) + list(APPEND WPE_LIB_DIRS ${WPE_WEBKIT_LIBRARY_DIRS}) +endif() +if(LIBWPE_LIBRARY_DIRS) + list(APPEND WPE_LIB_DIRS ${LIBWPE_LIBRARY_DIRS}) +endif() +if(HAVE_WPE_FDO AND WPE_FDO_LIBRARY_DIRS) + list(APPEND WPE_LIB_DIRS ${WPE_FDO_LIBRARY_DIRS}) +endif() +# Add common paths as fallback +list(APPEND WPE_LIB_DIRS + "/usr/local/lib" + "/usr/local/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/usr/lib" + "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}" +) +list(REMOVE_DUPLICATES WPE_LIB_DIRS) + +# Find WPE WebKit library +find_and_add_library("libWPEWebKit-2.0" "${WPE_LIB_DIRS}" WPE_BUNDLED_LIBS) + +# Find libwpe library +find_and_add_library("libwpe-1.0" "${WPE_LIB_DIRS}" WPE_BUNDLED_LIBS) + +# WPEPlatform is built into libWPEWebKit-2.0 on modern builds, so there are +# no separate libWPEPlatform*.so files to bundle. + +# Find WPE Backend FDO library (legacy fallback) +if(HAVE_WPE_FDO) + find_and_add_library("libWPEBackend-fdo-1.0" "${WPE_LIB_DIRS}" WPE_BUNDLED_LIBS) +endif() + +# Also need to create symlinks for the sonames that the libraries expect +# This will be handled at install time in the example CMakeLists.txt + +set(flutter_inappwebview_linux_bundled_libraries + ${WPE_BUNDLED_LIBS} + PARENT_SCOPE +) + +# Export library info for downstream use +set(FLUTTER_INAPPWEBVIEW_WPE_LIBS ${WPE_BUNDLED_LIBS} CACHE INTERNAL "WPE libraries to bundle") + +# === Tests === +# These unit tests can be run from a terminal after building the example. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +if(${CMAKE_VERSION} VERSION_LESS "3.11.0") +message("Unit tests require CMake 3.11.0 or later") +else() +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) + +FetchContent_MakeAvailable(googletest) + +# The plugin's exported API is not very useful for unit testing, so build the +# sources directly into the test binary rather than using the shared library. +add_executable(${TEST_RUNNER} + test/flutter_inappwebview_linux_plugin_test.cc + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + ${EPOXY_INCLUDE_DIRS} + ${WPE_WEBKIT_INCLUDE_DIRS}) +target_link_libraries(${TEST_RUNNER} PRIVATE flutter) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::EPOXY) +target_link_libraries(${TEST_RUNNER} PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::WPE_WEBKIT) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::LIBWPE) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::WAYLAND_SERVER) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::LIBSECRET) +if(HAVE_WPE_PLATFORM) + target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::WPE_PLATFORM) + target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::WPE_PLATFORM_HEADLESS) +endif() +if(HAVE_WPE_FDO) + target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::WPE_FDO) +endif() +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) + +endif() # CMake version check +endif() # include_${PROJECT_NAME}_tests diff --git a/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.cc b/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.cc new file mode 100644 index 00000000..28101610 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.cc @@ -0,0 +1,352 @@ +#include "content_blocker_handler.h" + +#include + +#include +#include +#include + +#include "../utils/log.h" +#include "../utils/util.h" + +using json = nlohmann::json; + +namespace flutter_inappwebview_plugin { + +ContentBlockerHandler::ContentBlockerHandler(WebKitUserContentManager* content_manager) + : content_manager_(content_manager), + filter_store_(nullptr), + filter_identifier_("flutter_inappwebview_content_rules") { + if (content_manager_ == nullptr) { + errorLog("ContentBlockerHandler: content_manager is null"); + return; + } + + // Get app-specific ID for isolated storage + std::string app_id = resolve_application_id_sanitized(); + + // Create filter store in a cache directory + // Use XDG cache directory or fallback to /tmp + const char* xdg_cache = g_get_user_cache_dir(); + if (xdg_cache != nullptr) { + store_path_ = std::string(xdg_cache) + "/flutter_inappwebview/" + app_id + "/content_filters"; + } else { + store_path_ = "/tmp/flutter_inappwebview/" + app_id + "/content_filters"; + } + + // Ensure directory exists + std::error_code ec; + std::filesystem::create_directories(store_path_, ec); + if (ec) { + errorLog("ContentBlockerHandler: Failed to create filter store directory: " + ec.message()); + } + + // Create the filter store + filter_store_ = webkit_user_content_filter_store_new(store_path_.c_str()); + if (filter_store_ == nullptr) { + errorLog("ContentBlockerHandler: Failed to create WebKitUserContentFilterStore"); + } +} + +ContentBlockerHandler::~ContentBlockerHandler() { + removeAllFilters(); + + if (filter_store_ != nullptr) { + g_object_unref(filter_store_); + filter_store_ = nullptr; + } +} + +void ContentBlockerHandler::setContentBlockers(FlValue* contentBlockers, + std::function callback) { + if (filter_store_ == nullptr || content_manager_ == nullptr) { + errorLog("ContentBlockerHandler: filter_store or content_manager is null"); + if (callback) callback(false); + return; + } + + // First remove existing filters + removeAllFilters(); + + // Check if there are any content blockers to apply + if (contentBlockers == nullptr || + fl_value_get_type(contentBlockers) != FL_VALUE_TYPE_LIST || + fl_value_get_length(contentBlockers) == 0) { + // No blockers - this is success (empty set) + if (callback) callback(true); + return; + } + + // Convert to JSON string + std::string jsonSource = convertToJsonString(contentBlockers); + + if (jsonSource.empty()) { + errorLog("ContentBlockerHandler: Failed to convert content blockers to JSON"); + if (callback) callback(false); + return; + } + + // Compile the content blockers + compileContentBlockers(jsonSource, callback); +} + +void ContentBlockerHandler::removeAllFilters() { + // Check if content_manager is still valid before calling WebKit API + // The content_manager becomes invalid when the webview is destroyed + if (content_manager_ != nullptr && WEBKIT_IS_USER_CONTENT_MANAGER(content_manager_)) { + webkit_user_content_manager_remove_all_filters(content_manager_); + } +} + +std::string ContentBlockerHandler::convertToJsonString(FlValue* contentBlockers) { + if (contentBlockers == nullptr || + fl_value_get_type(contentBlockers) != FL_VALUE_TYPE_LIST) { + return ""; + } + + json jsonArray = json::array(); + + size_t count = fl_value_get_length(contentBlockers); + for (size_t i = 0; i < count; i++) { + FlValue* blocker = fl_value_get_list_value(contentBlockers, i); + if (fl_value_get_type(blocker) != FL_VALUE_TYPE_MAP) { + continue; + } + + json rule; + + // Parse trigger + FlValue* triggerValue = fl_value_lookup_string(blocker, "trigger"); + if (triggerValue != nullptr && fl_value_get_type(triggerValue) == FL_VALUE_TYPE_MAP) { + json trigger; + + // url-filter (required) + FlValue* urlFilter = fl_value_lookup_string(triggerValue, "url-filter"); + if (urlFilter != nullptr && fl_value_get_type(urlFilter) == FL_VALUE_TYPE_STRING) { + trigger["url-filter"] = fl_value_get_string(urlFilter); + } else { + // Skip this rule - url-filter is required + continue; + } + + // url-filter-is-case-sensitive + FlValue* caseSensitive = fl_value_lookup_string(triggerValue, "url-filter-is-case-sensitive"); + if (caseSensitive != nullptr && fl_value_get_type(caseSensitive) == FL_VALUE_TYPE_BOOL) { + if (fl_value_get_bool(caseSensitive)) { + trigger["url-filter-is-case-sensitive"] = true; + } + } + + // resource-type (array) + FlValue* resourceType = fl_value_lookup_string(triggerValue, "resource-type"); + if (resourceType != nullptr && fl_value_get_type(resourceType) == FL_VALUE_TYPE_LIST) { + json types = json::array(); + for (size_t j = 0; j < fl_value_get_length(resourceType); j++) { + FlValue* type = fl_value_get_list_value(resourceType, j); + if (fl_value_get_type(type) == FL_VALUE_TYPE_STRING) { + types.push_back(fl_value_get_string(type)); + } + } + if (!types.empty()) { + trigger["resource-type"] = types; + } + } + + // if-domain (array) + FlValue* ifDomain = fl_value_lookup_string(triggerValue, "if-domain"); + if (ifDomain != nullptr && fl_value_get_type(ifDomain) == FL_VALUE_TYPE_LIST) { + json domains = json::array(); + for (size_t j = 0; j < fl_value_get_length(ifDomain); j++) { + FlValue* domain = fl_value_get_list_value(ifDomain, j); + if (fl_value_get_type(domain) == FL_VALUE_TYPE_STRING) { + domains.push_back(fl_value_get_string(domain)); + } + } + if (!domains.empty()) { + trigger["if-domain"] = domains; + } + } + + // unless-domain (array) + FlValue* unlessDomain = fl_value_lookup_string(triggerValue, "unless-domain"); + if (unlessDomain != nullptr && fl_value_get_type(unlessDomain) == FL_VALUE_TYPE_LIST) { + json domains = json::array(); + for (size_t j = 0; j < fl_value_get_length(unlessDomain); j++) { + FlValue* domain = fl_value_get_list_value(unlessDomain, j); + if (fl_value_get_type(domain) == FL_VALUE_TYPE_STRING) { + domains.push_back(fl_value_get_string(domain)); + } + } + if (!domains.empty()) { + trigger["unless-domain"] = domains; + } + } + + // load-type (array) + FlValue* loadType = fl_value_lookup_string(triggerValue, "load-type"); + if (loadType != nullptr && fl_value_get_type(loadType) == FL_VALUE_TYPE_LIST) { + json types = json::array(); + for (size_t j = 0; j < fl_value_get_length(loadType); j++) { + FlValue* type = fl_value_get_list_value(loadType, j); + if (fl_value_get_type(type) == FL_VALUE_TYPE_STRING) { + types.push_back(fl_value_get_string(type)); + } + } + if (!types.empty()) { + trigger["load-type"] = types; + } + } + + // if-top-url (array) + FlValue* ifTopUrl = fl_value_lookup_string(triggerValue, "if-top-url"); + if (ifTopUrl != nullptr && fl_value_get_type(ifTopUrl) == FL_VALUE_TYPE_LIST) { + json urls = json::array(); + for (size_t j = 0; j < fl_value_get_length(ifTopUrl); j++) { + FlValue* url = fl_value_get_list_value(ifTopUrl, j); + if (fl_value_get_type(url) == FL_VALUE_TYPE_STRING) { + urls.push_back(fl_value_get_string(url)); + } + } + if (!urls.empty()) { + trigger["if-top-url"] = urls; + } + } + + // unless-top-url (array) + FlValue* unlessTopUrl = fl_value_lookup_string(triggerValue, "unless-top-url"); + if (unlessTopUrl != nullptr && fl_value_get_type(unlessTopUrl) == FL_VALUE_TYPE_LIST) { + json urls = json::array(); + for (size_t j = 0; j < fl_value_get_length(unlessTopUrl); j++) { + FlValue* url = fl_value_get_list_value(unlessTopUrl, j); + if (fl_value_get_type(url) == FL_VALUE_TYPE_STRING) { + urls.push_back(fl_value_get_string(url)); + } + } + if (!urls.empty()) { + trigger["unless-top-url"] = urls; + } + } + + // if-frame-url (array) - WPE WebKit supports this + FlValue* ifFrameUrl = fl_value_lookup_string(triggerValue, "if-frame-url"); + if (ifFrameUrl != nullptr && fl_value_get_type(ifFrameUrl) == FL_VALUE_TYPE_LIST) { + json urls = json::array(); + for (size_t j = 0; j < fl_value_get_length(ifFrameUrl); j++) { + FlValue* url = fl_value_get_list_value(ifFrameUrl, j); + if (fl_value_get_type(url) == FL_VALUE_TYPE_STRING) { + urls.push_back(fl_value_get_string(url)); + } + } + if (!urls.empty()) { + trigger["if-frame-url"] = urls; + } + } + + rule["trigger"] = trigger; + } + + // Parse action + FlValue* actionValue = fl_value_lookup_string(blocker, "action"); + if (actionValue != nullptr && fl_value_get_type(actionValue) == FL_VALUE_TYPE_MAP) { + json action; + + // type (required) + FlValue* type = fl_value_lookup_string(actionValue, "type"); + if (type != nullptr && fl_value_get_type(type) == FL_VALUE_TYPE_STRING) { + std::string typeStr = fl_value_get_string(type); + + // WPE WebKit supports: block, block-cookies, css-display-none, + // ignore-previous-rules, make-https + action["type"] = typeStr; + } else { + // Skip this rule - type is required + continue; + } + + // selector (for css-display-none) + FlValue* selector = fl_value_lookup_string(actionValue, "selector"); + if (selector != nullptr && fl_value_get_type(selector) == FL_VALUE_TYPE_STRING) { + action["selector"] = fl_value_get_string(selector); + } + + rule["action"] = action; + } + + if (rule.contains("trigger") && rule.contains("action")) { + jsonArray.push_back(rule); + } + } + + if (jsonArray.empty()) { + return ""; + } + + return jsonArray.dump(); +} + +void ContentBlockerHandler::compileContentBlockers(const std::string& jsonSource, + std::function callback) { + if (filter_store_ == nullptr) { + if (callback) callback(false); + return; + } + + // Create GBytes from JSON source + GBytes* sourceBytes = g_bytes_new(jsonSource.c_str(), jsonSource.length()); + + // Create context for async callback + auto* context = new CompileContext{this, std::move(callback)}; + + // Start async compilation + webkit_user_content_filter_store_save(filter_store_, filter_identifier_.c_str(), sourceBytes, + nullptr, // cancellable + onFilterCompiled, context); + + g_bytes_unref(sourceBytes); +} + +void ContentBlockerHandler::onFilterCompiled(GObject* source, GAsyncResult* result, + gpointer user_data) { + auto* context = static_cast(user_data); + if (context == nullptr) { + return; + } + + auto* handler = context->handler; + auto callback = std::move(context->callback); + delete context; + + if (handler == nullptr || handler->filter_store_ == nullptr) { + if (callback) callback(false); + return; + } + + GError* error = nullptr; + WebKitUserContentFilter* filter = + webkit_user_content_filter_store_save_finish(handler->filter_store_, result, &error); + + if (error != nullptr) { + errorLog("ContentBlockerHandler: Failed to compile content blockers: " + + std::string(error->message)); + g_error_free(error); + if (callback) callback(false); + return; + } + + if (filter == nullptr) { + errorLog("ContentBlockerHandler: Compiled filter is null"); + if (callback) callback(false); + return; + } + + // Add the compiled filter to the content manager + if (handler->content_manager_ != nullptr) { + webkit_user_content_manager_add_filter(handler->content_manager_, filter); + } + + webkit_user_content_filter_unref(filter); + + if (callback) callback(true); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.h b/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.h new file mode 100644 index 00000000..5e747827 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/content_blocker/content_blocker_handler.h @@ -0,0 +1,102 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_BLOCKER_HANDLER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_BLOCKER_HANDLER_H_ + +#include +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class InAppWebView; + +/** + * ContentBlockerHandler manages WebKit content filters for content blocking. + * + * Uses WebKitUserContentFilterStore to compile Safari-compatible content blocker + * JSON rules into native WebKit filters. This is the same mechanism used by + * iOS/macOS (WKContentRuleListStore). + * + * WPE WebKit uses the Safari content blocker JSON format: + * [ + * { + * "trigger": { "url-filter": ".*ads.*" }, + * "action": { "type": "block" } + * } + * ] + * + * Supported action types: + * - block: Block the resource from loading + * - css-display-none: Hide matching elements with CSS + * - make-https: Upgrade HTTP URLs to HTTPS + */ +class ContentBlockerHandler { + public: + explicit ContentBlockerHandler(WebKitUserContentManager* content_manager); + ~ContentBlockerHandler(); + + /** + * Compile and apply content blockers from FlValue list. + * + * The FlValue should be a list of content blocker maps, each with: + * - "trigger": map with "url-filter" and optional other trigger properties + * - "action": map with "type" and optional "selector" (for css-display-none) + * + * @param contentBlockers FlValue list of content blocker maps + * @param callback Called when compilation is complete (success or failure) + */ + void setContentBlockers(FlValue* contentBlockers, + std::function callback); + + /** + * Remove all content filters from the content manager. + */ + void removeAllFilters(); + + /** + * Get the current filter identifier. + */ + std::string getFilterIdentifier() const { return filter_identifier_; } + + private: + /** + * Convert FlValue content blockers to Safari-format JSON string. + * + * @param contentBlockers FlValue list of content blocker maps + * @return JSON string in Safari content blocker format, or empty string on error + */ + std::string convertToJsonString(FlValue* contentBlockers); + + /** + * Compile JSON to WebKit filter (async). + * + * @param jsonSource JSON string in Safari format + * @param callback Called when compilation is complete + */ + void compileContentBlockers(const std::string& jsonSource, + std::function callback); + + /** + * Callback when filter store save completes. + */ + static void onFilterCompiled(GObject* source, GAsyncResult* result, gpointer user_data); + + WebKitUserContentManager* content_manager_; // Not owned (from webview) + WebKitUserContentFilterStore* filter_store_; // Owned + std::string filter_identifier_; + std::string store_path_; + + /** + * Context for async filter compilation callback. + */ + struct CompileContext { + ContentBlockerHandler* handler; + std::function callback; + }; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_BLOCKER_HANDLER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/cookie_manager.cc b/.vendor/flutter_inappwebview_linux/linux/cookie_manager.cc new file mode 100644 index 00000000..0fe3bd71 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/cookie_manager.cc @@ -0,0 +1,607 @@ +#include "cookie_manager.h" + +#include +#include +#include + +#include "plugin_instance.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} + +// Parse SameSite attribute from string +SoupSameSitePolicy parseSameSite(const std::string& sameSite) { + if (sameSite == "Strict") { + return SOUP_SAME_SITE_POLICY_STRICT; + } else if (sameSite == "Lax") { + return SOUP_SAME_SITE_POLICY_LAX; + } + return SOUP_SAME_SITE_POLICY_NONE; +} + +// Convert SameSite policy to string +std::string sameSiteToString(SoupSameSitePolicy policy) { + switch (policy) { + case SOUP_SAME_SITE_POLICY_STRICT: + return "Strict"; + case SOUP_SAME_SITE_POLICY_LAX: + return "Lax"; + case SOUP_SAME_SITE_POLICY_NONE: + default: + return "None"; + } +} +} // namespace + +// === Cookie === + +Cookie::Cookie() : isSecure(false), isHttpOnly(false) {} + +Cookie::Cookie(const std::string& name, const std::string& value) + : name(name), value(value), isSecure(false), isHttpOnly(false) {} + +Cookie::Cookie(SoupCookie* soupCookie) : isSecure(false), isHttpOnly(false) { + if (soupCookie == nullptr) { + return; + } + + const char* n = soup_cookie_get_name(soupCookie); + const char* v = soup_cookie_get_value(soupCookie); + const char* d = soup_cookie_get_domain(soupCookie); + const char* p = soup_cookie_get_path(soupCookie); + + if (n != nullptr) + name = std::string(n); + if (v != nullptr) + value = std::string(v); + if (d != nullptr) + domain = std::string(d); + if (p != nullptr) + path = std::string(p); + + GDateTime* expires = soup_cookie_get_expires(soupCookie); + if (expires != nullptr) { + gint64 unix_time = g_date_time_to_unix(expires); + expiresDate = unix_time * 1000; // Convert to milliseconds + } + + isSecure = soup_cookie_get_secure(soupCookie); + isHttpOnly = soup_cookie_get_http_only(soupCookie); + sameSite = sameSiteToString(soup_cookie_get_same_site_policy(soupCookie)); +} + +Cookie::Cookie(FlValue* map) : isSecure(false), isHttpOnly(false) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + name = get_fl_map_value(map, "name", ""); + value = get_fl_map_value(map, "value", ""); + domain = get_optional_fl_map_value(map, "domain"); + path = get_optional_fl_map_value(map, "path"); + + FlValue* expiresValue = fl_value_lookup_string(map, "expiresDate"); + if (expiresValue != nullptr && fl_value_get_type(expiresValue) == FL_VALUE_TYPE_INT) { + expiresDate = fl_value_get_int(expiresValue); + } + + isSecure = get_fl_map_value(map, "isSecure", false); + isHttpOnly = get_fl_map_value(map, "isHttpOnly", false); + sameSite = get_optional_fl_map_value(map, "sameSite"); +} + +SoupCookie* Cookie::toSoupCookie(const std::string& url) const { + // Parse URL to get domain if not provided + GUri* guri = g_uri_parse(url.c_str(), G_URI_FLAGS_NONE, nullptr); + const char* host = nullptr; + if (guri != nullptr) { + host = g_uri_get_host(guri); + } + + std::string cookieDomain = domain.value_or(host ? std::string(host) : ""); + std::string cookiePath = path.value_or("/"); + + SoupCookie* cookie = + soup_cookie_new(name.c_str(), value.c_str(), cookieDomain.c_str(), cookiePath.c_str(), + -1 // maxAge (-1 = session cookie) + ); + + if (cookie != nullptr) { + if (expiresDate.has_value()) { + gint64 unix_time = expiresDate.value() / 1000; // Convert from milliseconds + GDateTime* expires = g_date_time_new_from_unix_utc(unix_time); + if (expires != nullptr) { + soup_cookie_set_expires(cookie, expires); + g_date_time_unref(expires); + } + } + + soup_cookie_set_secure(cookie, isSecure); + soup_cookie_set_http_only(cookie, isHttpOnly); + + if (sameSite.has_value()) { + soup_cookie_set_same_site_policy(cookie, parseSameSite(sameSite.value())); + } + } + + if (guri != nullptr) { + g_uri_unref(guri); + } + + return cookie; +} + +FlValue* Cookie::toFlValue() const { + return to_fl_map({ + {"name", make_fl_value(name)}, + {"value", make_fl_value(value)}, + {"domain", make_fl_value(domain)}, + {"path", make_fl_value(path)}, + {"expiresDate", make_fl_value(expiresDate)}, + {"isSecure", make_fl_value(isSecure)}, + {"isHttpOnly", make_fl_value(isHttpOnly)}, + {"sameSite", make_fl_value(sameSite)}, + }); +} + +// === CookieManager === + +CookieManager::CookieManager(PluginInstance* plugin) + : ChannelDelegate(plugin->messenger(), METHOD_CHANNEL_NAME), + plugin_(plugin), + cookie_manager_(nullptr) {} + +CookieManager::~CookieManager() { + debugLog("dealloc CookieManager"); + plugin_ = nullptr; +} + +WebKitCookieManager* CookieManager::getCookieManager() { + if (cookie_manager_ == nullptr) { + // WPE WebKit 2.x uses NetworkSession API instead of WebContext + WebKitNetworkSession* session = webkit_network_session_get_default(); + if (session != nullptr) { + cookie_manager_ = webkit_network_session_get_cookie_manager(session); + } + } + return cookie_manager_; +} + +void CookieManager::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(method, "setCookie")) { + std::string url = get_fl_map_value(args, "url", ""); + FlValue* cookieMap = fl_value_lookup_string(args, "cookie"); + + if (url.empty() || cookieMap == nullptr) { + fl_method_call_respond_success(method_call, fl_value_new_bool(FALSE), nullptr); + return; + } + + Cookie cookie(cookieMap); + + g_object_ref(method_call); + setCookie(url, cookie, [method_call](bool success) { + fl_method_call_respond_success(method_call, fl_value_new_bool(success), nullptr); + g_object_unref(method_call); + }); + } else if (string_equals(method, "getCookies")) { + std::string url = get_fl_map_value(args, "url", ""); + + if (url.empty()) { + fl_method_call_respond_success(method_call, fl_value_new_list(), nullptr); + return; + } + + g_object_ref(method_call); + getCookies(url, [method_call](std::vector cookies) { + g_autoptr(FlValue) result = fl_value_new_list(); + for (const auto& cookie : cookies) { + fl_value_append_take(result, cookie.toFlValue()); + } + fl_method_call_respond_success(method_call, result, nullptr); + g_object_unref(method_call); + }); + } else if (string_equals(method, "getCookie")) { + std::string url = get_fl_map_value(args, "url", ""); + std::string name = get_fl_map_value(args, "name", ""); + + if (url.empty() || name.empty()) { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + return; + } + + g_object_ref(method_call); + getCookie(url, name, [method_call](std::optional cookie) { + if (cookie.has_value()) { + fl_method_call_respond_success(method_call, cookie.value().toFlValue(), nullptr); + } else { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + } + g_object_unref(method_call); + }); + } else if (string_equals(method, "deleteCookie")) { + std::string url = get_fl_map_value(args, "url", ""); + std::string name = get_fl_map_value(args, "name", ""); + std::string domain = get_fl_map_value(args, "domain", ""); + std::string path = get_fl_map_value(args, "path", "/"); + + g_object_ref(method_call); + deleteCookie(url, name, domain, path, [method_call](bool success) { + fl_method_call_respond_success(method_call, fl_value_new_bool(success), nullptr); + g_object_unref(method_call); + }); + } else if (string_equals(method, "deleteCookies")) { + std::string url = get_fl_map_value(args, "url", ""); + std::string domain = get_fl_map_value(args, "domain", ""); + std::string path = get_fl_map_value(args, "path", "/"); + + g_object_ref(method_call); + deleteCookies(url, domain, path, [method_call](bool success) { + fl_method_call_respond_success(method_call, fl_value_new_bool(success), nullptr); + g_object_unref(method_call); + }); + } else if (string_equals(method, "deleteAllCookies")) { + g_object_ref(method_call); + deleteAllCookies([method_call](bool success) { + fl_method_call_respond_success(method_call, fl_value_new_bool(success), nullptr); + g_object_unref(method_call); + }); + } else if (string_equals(method, "getAllCookies")) { + g_object_ref(method_call); + getAllCookies([method_call](std::vector cookies) { + g_autoptr(FlValue) result = fl_value_new_list(); + for (const auto& cookie : cookies) { + fl_value_append_take(result, cookie.toFlValue()); + } + fl_method_call_respond_success(method_call, result, nullptr); + g_object_unref(method_call); + }); + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +void CookieManager::setCookie(const std::string& url, const Cookie& cookie, + std::function callback) { + WebKitCookieManager* manager = getCookieManager(); + if (manager == nullptr) { + callback(false); + return; + } + + SoupCookie* soupCookie = cookie.toSoupCookie(url); + if (soupCookie == nullptr) { + callback(false); + return; + } + + // Store callback in a closure + auto* callbackPtr = new std::function(std::move(callback)); + + webkit_cookie_manager_add_cookie( + manager, soupCookie, + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* cb = static_cast*>(user_data); + + GError* error = nullptr; + gboolean success = + webkit_cookie_manager_add_cookie_finish(WEBKIT_COOKIE_MANAGER(source), result, &error); + + if (error != nullptr) { + errorLog(std::string("CookieManager: setCookie failed: ") + error->message); + g_error_free(error); + } + + (*cb)(success); + delete cb; + }, + callbackPtr); + + soup_cookie_free(soupCookie); +} + +void CookieManager::getCookies(const std::string& url, + std::function)> callback) { + WebKitCookieManager* manager = getCookieManager(); + if (manager == nullptr) { + callback({}); + return; + } + + auto* callbackPtr = new std::function)>(std::move(callback)); + + webkit_cookie_manager_get_cookies( + manager, url.c_str(), + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* cb = static_cast)>*>(user_data); + + GError* error = nullptr; + GList* cookies = + webkit_cookie_manager_get_cookies_finish(WEBKIT_COOKIE_MANAGER(source), result, &error); + + std::vector cookieList; + + if (error != nullptr) { + errorLog(std::string("CookieManager: getCookies failed: ") + error->message); + g_error_free(error); + } else if (cookies != nullptr) { + for (GList* l = cookies; l != nullptr; l = l->next) { + SoupCookie* soupCookie = static_cast(l->data); + cookieList.emplace_back(soupCookie); + } + g_list_free_full(cookies, reinterpret_cast(soup_cookie_free)); + } + + (*cb)(cookieList); + delete cb; + }, + callbackPtr); +} + +void CookieManager::getCookie(const std::string& url, const std::string& name, + std::function)> callback) { + getCookies(url, [name, callback = std::move(callback)](std::vector cookies) { + for (const auto& cookie : cookies) { + if (cookie.name == name) { + callback(cookie); + return; + } + } + callback(std::nullopt); + }); +} + +void CookieManager::deleteCookie(const std::string& url, const std::string& name, + const std::string& domain, const std::string& path, + std::function callback) { + WebKitCookieManager* manager = getCookieManager(); + if (manager == nullptr) { + callback(false); + return; + } + + // Determine the domain to use for fetching cookies + std::string cookieDomain = domain; + if (cookieDomain.empty() && !url.empty()) { + GUri* guri = g_uri_parse(url.c_str(), G_URI_FLAGS_NONE, nullptr); + if (guri != nullptr) { + const char* host = g_uri_get_host(guri); + if (host != nullptr) { + cookieDomain = std::string(host); + } + g_uri_unref(guri); + } + } + + // WPE WebKit requires the EXACT SoupCookie object to delete, not a minimal one. + // We must first fetch all cookies and find the matching one with all its attributes. + auto* callbackPtr = new std::function(std::move(callback)); + + // Capture parameters for the callback + struct DeleteContext { + WebKitCookieManager* manager; + std::string name; + std::string domain; + std::string path; + std::function* callback; + }; + + auto* ctx = new DeleteContext{manager, name, cookieDomain, path, callbackPtr}; + + webkit_cookie_manager_get_all_cookies( + manager, + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* ctx = static_cast(user_data); + + GError* error = nullptr; + GList* cookies = webkit_cookie_manager_get_all_cookies_finish( + WEBKIT_COOKIE_MANAGER(source), result, &error); + + if (error != nullptr) { + errorLog(std::string("CookieManager: deleteCookie fetch failed: ") + error->message); + g_error_free(error); + (*(ctx->callback))(false); + delete ctx->callback; + delete ctx; + return; + } + + // Find the matching cookie + SoupCookie* matchingCookie = nullptr; + for (GList* l = cookies; l != nullptr; l = l->next) { + SoupCookie* soupCookie = static_cast(l->data); + const char* cookieName = soup_cookie_get_name(soupCookie); + const char* cookieDomain = soup_cookie_get_domain(soupCookie); + const char* cookiePath = soup_cookie_get_path(soupCookie); + + if (cookieName != nullptr && strcmp(cookieName, ctx->name.c_str()) == 0) { + // Check domain match (if specified) + bool domainMatch = ctx->domain.empty() || + (cookieDomain != nullptr && + (strcmp(cookieDomain, ctx->domain.c_str()) == 0 || + // Also match with leading dot (e.g., ".example.com" matches "example.com") + (cookieDomain[0] == '.' && strcmp(cookieDomain + 1, ctx->domain.c_str()) == 0) || + (ctx->domain[0] == '.' && strcmp(cookieDomain, ctx->domain.c_str() + 1) == 0))); + + // Check path match (if not default) + bool pathMatch = ctx->path == "/" || + (cookiePath != nullptr && strcmp(cookiePath, ctx->path.c_str()) == 0); + + if (domainMatch && pathMatch) { + matchingCookie = soup_cookie_copy(soupCookie); + break; + } + } + } + + g_list_free_full(cookies, reinterpret_cast(soup_cookie_free)); + + if (matchingCookie == nullptr) { + // Cookie not found - consider this a success (nothing to delete) + (*(ctx->callback))(true); + delete ctx->callback; + delete ctx; + return; + } + + // Now delete the actual cookie with all its attributes + webkit_cookie_manager_delete_cookie( + ctx->manager, matchingCookie, + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* ctx = static_cast(user_data); + + GError* error = nullptr; + gboolean success = webkit_cookie_manager_delete_cookie_finish( + WEBKIT_COOKIE_MANAGER(source), result, &error); + + if (error != nullptr) { + errorLog(std::string("CookieManager: deleteCookie failed: ") + error->message); + g_error_free(error); + } + + (*(ctx->callback))(success); + delete ctx->callback; + delete ctx; + }, + ctx); + + soup_cookie_free(matchingCookie); + }, + ctx); +} + +void CookieManager::deleteCookies(const std::string& url, const std::string& domain, + const std::string& path, std::function callback) { + // Get all cookies for the URL and delete them + getCookies(url, + [this, domain, path, callback = std::move(callback)](std::vector cookies) { + if (cookies.empty()) { + callback(true); + return; + } + + auto* remaining = new std::atomic(cookies.size()); + auto* anyFailed = new std::atomic(false); + auto* sharedCallback = new std::function(callback); + + for (const auto& cookie : cookies) { + std::string cookieDomain = cookie.domain.value_or(""); + std::string cookiePath = cookie.path.value_or("/"); + + // Filter by domain if specified + if (!domain.empty() && cookieDomain != domain) { + size_t prev = remaining->fetch_sub(1); + if (prev == 1) { + (*sharedCallback)(!(*anyFailed)); + delete remaining; + delete anyFailed; + delete sharedCallback; + } + continue; + } + + deleteCookie("", cookie.name, cookieDomain, cookiePath, + [remaining, anyFailed, sharedCallback](bool success) { + if (!success) { + anyFailed->store(true); + } + size_t prev = remaining->fetch_sub(1); + if (prev == 1) { + (*sharedCallback)(!(*anyFailed)); + delete remaining; + delete anyFailed; + delete sharedCallback; + } + }); + } + }); +} + +void CookieManager::deleteAllCookies(std::function callback) { + // WPE WebKit 2.x uses NetworkSession API + WebKitNetworkSession* session = webkit_network_session_get_default(); + WebKitWebsiteDataManager* manager = + session != nullptr ? webkit_network_session_get_website_data_manager(session) : nullptr; + + if (manager == nullptr) { + callback(false); + return; + } + + auto* callbackPtr = new std::function(std::move(callback)); + + webkit_website_data_manager_clear( + manager, WEBKIT_WEBSITE_DATA_COOKIES, + 0, // timespan (0 = all) + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* cb = static_cast*>(user_data); + + GError* error = nullptr; + gboolean success = webkit_website_data_manager_clear_finish( + WEBKIT_WEBSITE_DATA_MANAGER(source), result, &error); + + if (error != nullptr) { + errorLog(std::string("CookieManager: deleteAllCookies failed: ") + error->message); + g_error_free(error); + } + + (*cb)(success); + delete cb; + }, + callbackPtr); +} + +void CookieManager::getAllCookies(std::function)> callback) { + WebKitCookieManager* manager = getCookieManager(); + if (manager == nullptr) { + callback({}); + return; + } + + auto* callbackPtr = new std::function)>(std::move(callback)); + + webkit_cookie_manager_get_all_cookies( + manager, + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* cb = static_cast)>*>(user_data); + + GError* error = nullptr; + GList* cookies = webkit_cookie_manager_get_all_cookies_finish( + WEBKIT_COOKIE_MANAGER(source), result, &error); + + std::vector cookieList; + + if (error != nullptr) { + errorLog(std::string("CookieManager: getAllCookies failed: ") + error->message); + g_error_free(error); + } else if (cookies != nullptr) { + for (GList* l = cookies; l != nullptr; l = l->next) { + SoupCookie* soupCookie = static_cast(l->data); + cookieList.emplace_back(soupCookie); + } + g_list_free_full(cookies, reinterpret_cast(soup_cookie_free)); + } + + (*cb)(cookieList); + delete cb; + }, + callbackPtr); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/cookie_manager.h b/.vendor/flutter_inappwebview_linux/linux/cookie_manager.h new file mode 100644 index 00000000..dfe579fd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/cookie_manager.h @@ -0,0 +1,87 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/** + * Represents a cookie. + */ +class Cookie { + public: + std::string name; + std::string value; + std::optional domain; + std::optional path; + std::optional expiresDate; // Unix timestamp in milliseconds + bool isSecure; + bool isHttpOnly; + std::optional sameSite; + + Cookie(); + Cookie(const std::string& name, const std::string& value); + Cookie(SoupCookie* soupCookie); + Cookie(FlValue* map); + ~Cookie() = default; + + SoupCookie* toSoupCookie(const std::string& url) const; + FlValue* toFlValue() const; +}; + +/** + * Manages cookies for WebKitGTK. + * Uses WebKitCookieManager and libsoup for cookie operations. + */ +class CookieManager : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_inappwebview_cookiemanager"; + + CookieManager(PluginInstance* plugin); + ~CookieManager() override; + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + void HandleMethodCall(FlMethodCall* method_call) override; + + // Cookie operations + void setCookie(const std::string& url, const Cookie& cookie, std::function callback); + + void getCookies(const std::string& url, std::function)> callback); + + void getCookie(const std::string& url, const std::string& name, + std::function)> callback); + + void deleteCookie(const std::string& url, const std::string& name, const std::string& domain, + const std::string& path, std::function callback); + + void deleteCookies(const std::string& url, const std::string& domain, const std::string& path, + std::function callback); + + void deleteAllCookies(std::function callback); + + void getAllCookies(std::function)> callback); + + private: + PluginInstance* plugin_ = nullptr; + WebKitCookieManager* cookie_manager_; + + WebKitCookieManager* getCookieManager(); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/credential_database.cc b/.vendor/flutter_inappwebview_linux/linux/credential_database.cc new file mode 100644 index 00000000..ead9cdcf --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/credential_database.cc @@ -0,0 +1,594 @@ +#include "credential_database.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include "plugin_instance.h" +#include "utils/flutter.h" +#include "utils/log.h" +#include "utils/util.h" + +namespace flutter_inappwebview_plugin { + +using json = nlohmann::json; + +namespace { +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +// === Schema Definition === + +const SecretSchema* CredentialDatabase::getSchema() { + static const SecretSchema schema = { + "com.pichillilorenzo.flutter_inappwebview.HttpAuth", + SECRET_SCHEMA_NONE, + { + {"appId", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"host", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"port", SECRET_SCHEMA_ATTRIBUTE_INTEGER}, + {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"realm", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"username", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {NULL, (SecretSchemaAttributeType)0} + } + }; + return &schema; +} + +// === ProtectionSpace === + +ProtectionSpace::ProtectionSpace(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + host = get_fl_map_value(map, "host", ""); + port = get_fl_map_value(map, "port", 0); + protocol = get_optional_fl_map_value(map, "protocol"); + realm = get_optional_fl_map_value(map, "realm"); +} + +FlValue* ProtectionSpace::toFlValue() const { + return to_fl_map({ + {"host", make_fl_value(host)}, + {"port", make_fl_value(port)}, + {"protocol", make_fl_value(protocol)}, + {"realm", make_fl_value(realm)}, + }); +} + +bool ProtectionSpace::operator==(const ProtectionSpace& other) const { + return host == other.host && port == other.port && + protocol == other.protocol && realm == other.realm; +} + +// === Credential === + +Credential::Credential(const std::string& username, const std::string& password) + : username(username), password(password) {} + +Credential::Credential(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + username = get_fl_map_value(map, "username", ""); + password = get_fl_map_value(map, "password", ""); +} + +FlValue* Credential::toFlValue() const { + return to_fl_map({ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)}, + }); +} + +// === CredentialDatabase === + +std::string CredentialDatabase::makeKey(const ProtectionSpace& ps) { + std::ostringstream oss; + oss << ps.host << ":" << ps.port << ":" + << ps.protocol.value_or("") << ":" + << ps.realm.value_or(""); + return oss.str(); +} + +std::string CredentialDatabase::makeLabel(const ProtectionSpace& ps, const std::string& username) { + std::ostringstream oss; + oss << "InAppWebView: " << username << "@" << ps.host; + if (ps.port != 0 && ps.port != 80 && ps.port != 443) { + oss << ":" << ps.port; + } + if (ps.realm.has_value() && !ps.realm->empty()) { + oss << " (" << ps.realm.value() << ")"; + } + return oss.str(); +} + +ProtectionSpace CredentialDatabase::fromKey(const std::string& key) { + ProtectionSpace ps; + std::vector parts; + std::istringstream iss(key); + std::string part; + + // Split by ':' + while (std::getline(iss, part, ':')) { + parts.push_back(part); + } + + // Handle case where key ends with colons (empty parts at end) + size_t colonCount = std::count(key.begin(), key.end(), ':'); + while (parts.size() <= colonCount) { + parts.push_back(""); + } + + if (parts.size() > 0) { + ps.host = parts[0]; + } + if (parts.size() > 1 && !parts[1].empty()) { + try { + ps.port = std::stoi(parts[1]); + } catch (...) { + ps.port = 0; + } + } + if (parts.size() > 2 && !parts[2].empty()) { + ps.protocol = parts[2]; + } + if (parts.size() > 3 && !parts[3].empty()) { + ps.realm = parts[3]; + } + + return ps; +} + +std::string CredentialDatabase::getIndexPath(const std::string& app_id) { + const char* xdg_data_home = getenv("XDG_DATA_HOME"); + std::string base_dir; + + if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) { + base_dir = xdg_data_home; + } else { + const char* home = getenv("HOME"); + if (home != nullptr) { + base_dir = std::string(home) + "/.local/share"; + } else { + base_dir = "/tmp"; + } + } + + std::string dir = base_dir + "/flutter_inappwebview"; + mkdir(dir.c_str(), 0700); + + std::string app_dir = dir + "/" + app_id; + mkdir(app_dir.c_str(), 0700); + + return app_dir + "/credential_index.json"; +} + +void CredentialDatabase::loadIndex() { + credentials_index_.clear(); + + std::ifstream file(index_path_); + if (!file.is_open()) { + return; + } + + try { + json j; + file >> j; + + if (j.is_object()) { + for (auto& [key, usernames] : j.items()) { + std::vector users; + if (usernames.is_array()) { + for (auto& u : usernames) { + if (u.is_string()) { + users.push_back(u.get()); + } + } + } + credentials_index_[key] = users; + } + } + } catch (const std::exception& e) { + errorLog(std::string("CredentialDatabase: Failed to load index: ") + e.what()); + credentials_index_.clear(); + } +} + +void CredentialDatabase::saveIndex() { + json j = json::object(); + + for (const auto& [key, usernames] : credentials_index_) { + j[key] = usernames; + } + + std::ofstream file(index_path_); + if (file.is_open()) { + file << j.dump(2); + file.close(); + chmod(index_path_.c_str(), 0600); + } +} + +CredentialDatabase::CredentialDatabase(PluginInstance* plugin) + : ChannelDelegate(plugin->messenger(), METHOD_CHANNEL_NAME), + plugin_(plugin) { + app_id_ = resolve_application_id_sanitized(); + index_path_ = getIndexPath(app_id_); + loadIndex(); +} + +CredentialDatabase::~CredentialDatabase() { + debugLog("dealloc CredentialDatabase"); + plugin_ = nullptr; +} + +void CredentialDatabase::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(method, "getAllAuthCredentials")) { + auto all = getAllAuthCredentials(); + + g_autoptr(FlValue) result = fl_value_new_list(); + + for (const auto& [ps, creds] : all) { + FlValue* creds_list = fl_value_new_list(); + for (const auto& cred : creds) { + fl_value_append_take(creds_list, cred.toFlValue()); + } + + FlValue* entry = to_fl_map({ + {"protectionSpace", ps.toFlValue()}, + {"credentials", creds_list}, + }); + + fl_value_append_take(result, entry); + } + + fl_method_call_respond_success(method_call, result, nullptr); + + } else if (string_equals(method, "getHttpAuthCredentials")) { + std::string host = get_fl_map_value(args, "host", ""); + int port = get_fl_map_value(args, "port", 0); + auto protocol = get_optional_fl_map_value(args, "protocol"); + auto realm = get_optional_fl_map_value(args, "realm"); + + ProtectionSpace ps; + ps.host = host; + ps.port = port; + ps.protocol = protocol; + ps.realm = realm; + + auto credentials = getHttpAuthCredentials(ps); + + g_autoptr(FlValue) result = fl_value_new_list(); + for (const auto& cred : credentials) { + fl_value_append_take(result, cred.toFlValue()); + } + + fl_method_call_respond_success(method_call, result, nullptr); + + } else if (string_equals(method, "setHttpAuthCredential")) { + std::string host = get_fl_map_value(args, "host", ""); + int port = get_fl_map_value(args, "port", 0); + auto protocol = get_optional_fl_map_value(args, "protocol"); + auto realm = get_optional_fl_map_value(args, "realm"); + std::string username = get_fl_map_value(args, "username", ""); + std::string password = get_fl_map_value(args, "password", ""); + + ProtectionSpace ps; + ps.host = host; + ps.port = port; + ps.protocol = protocol; + ps.realm = realm; + + Credential cred(username, password); + + setHttpAuthCredential(ps, cred); + + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + + } else if (string_equals(method, "removeHttpAuthCredential")) { + std::string host = get_fl_map_value(args, "host", ""); + int port = get_fl_map_value(args, "port", 0); + auto protocol = get_optional_fl_map_value(args, "protocol"); + auto realm = get_optional_fl_map_value(args, "realm"); + std::string username = get_fl_map_value(args, "username", ""); + std::string password = get_fl_map_value(args, "password", ""); + + ProtectionSpace ps; + ps.host = host; + ps.port = port; + ps.protocol = protocol; + ps.realm = realm; + + Credential cred(username, password); + + removeHttpAuthCredential(ps, cred); + + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + + } else if (string_equals(method, "removeHttpAuthCredentials")) { + std::string host = get_fl_map_value(args, "host", ""); + int port = get_fl_map_value(args, "port", 0); + auto protocol = get_optional_fl_map_value(args, "protocol"); + auto realm = get_optional_fl_map_value(args, "realm"); + + ProtectionSpace ps; + ps.host = host; + ps.port = port; + ps.protocol = protocol; + ps.realm = realm; + + removeHttpAuthCredentials(ps); + + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + + } else if (string_equals(method, "clearAllAuthCredentials")) { + clearAllAuthCredentials(); + + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +std::vector>> +CredentialDatabase::getAllAuthCredentials() { + std::vector>> result; + + for (const auto& [key, usernames] : credentials_index_) { + // Parse key back to ProtectionSpace + ProtectionSpace ps = fromKey(key); + + // Lookup each credential from libsecret + std::vector creds; + for (const auto& username : usernames) { + auto cred = lookupCredential(ps, username); + if (cred.has_value()) { + creds.push_back(cred.value()); + } + } + + if (!creds.empty()) { + result.push_back({ps, creds}); + } + } + + return result; +} + +std::vector CredentialDatabase::getHttpAuthCredentials( + const ProtectionSpace& protectionSpace) { + std::string key = makeKey(protectionSpace); + + auto it = credentials_index_.find(key); + if (it == credentials_index_.end()) { + return {}; + } + + std::vector creds; + for (const auto& username : it->second) { + auto cred = lookupCredential(protectionSpace, username); + if (cred.has_value()) { + creds.push_back(cred.value()); + } + } + + return creds; +} + +std::optional CredentialDatabase::lookupCredential( + const ProtectionSpace& protectionSpace, const std::string& username) { + GError* error = nullptr; + + gchar* password = secret_password_lookup_sync( + getSchema(), + nullptr, + &error, + "appId", app_id_.c_str(), + "host", protectionSpace.host.c_str(), + "port", protectionSpace.port, + "protocol", protectionSpace.protocol.value_or("").c_str(), + "realm", protectionSpace.realm.value_or("").c_str(), + "username", username.c_str(), + NULL); + + if (error != nullptr) { + errorLog(std::string("CredentialDatabase: Failed to lookup credential: ") + error->message); + g_error_free(error); + return std::nullopt; + } + + if (password == nullptr) { + return std::nullopt; + } + + Credential cred(username, password); + secret_password_free(password); + + return cred; +} + +std::optional CredentialDatabase::lookupFirstCredential( + const ProtectionSpace& protectionSpace) { + std::string key = makeKey(protectionSpace); + + auto it = credentials_index_.find(key); + if (it == credentials_index_.end() || it->second.empty()) { + return std::nullopt; + } + + // Return the first available credential + for (const auto& username : it->second) { + auto cred = lookupCredential(protectionSpace, username); + if (cred.has_value()) { + return cred; + } + } + + return std::nullopt; +} + +void CredentialDatabase::setHttpAuthCredential(const ProtectionSpace& protectionSpace, + const Credential& credential) { + GError* error = nullptr; + + std::string label = makeLabel(protectionSpace, credential.username); + + gboolean stored = secret_password_store_sync( + getSchema(), + SECRET_COLLECTION_DEFAULT, + label.c_str(), + credential.password.c_str(), + nullptr, + &error, + "appId", app_id_.c_str(), + "host", protectionSpace.host.c_str(), + "port", protectionSpace.port, + "protocol", protectionSpace.protocol.value_or("").c_str(), + "realm", protectionSpace.realm.value_or("").c_str(), + "username", credential.username.c_str(), + NULL); + + if (error != nullptr) { + errorLog(std::string("CredentialDatabase: Failed to store credential: ") + error->message); + g_error_free(error); + return; + } + + if (!stored) { + errorLog("CredentialDatabase: Failed to store credential (unknown error)"); + return; + } + + // Update index + std::string key = makeKey(protectionSpace); + auto& usernames = credentials_index_[key]; + + // Add username if not already present + if (std::find(usernames.begin(), usernames.end(), credential.username) == usernames.end()) { + usernames.push_back(credential.username); + } + + saveIndex(); +} + +void CredentialDatabase::removeHttpAuthCredential(const ProtectionSpace& protectionSpace, + const Credential& credential) { + GError* error = nullptr; + + secret_password_clear_sync( + getSchema(), + nullptr, + &error, + "appId", app_id_.c_str(), + "host", protectionSpace.host.c_str(), + "port", protectionSpace.port, + "protocol", protectionSpace.protocol.value_or("").c_str(), + "realm", protectionSpace.realm.value_or("").c_str(), + "username", credential.username.c_str(), + NULL); + + if (error != nullptr) { + errorLog(std::string("CredentialDatabase: Failed to remove credential: ") + error->message); + g_error_free(error); + return; + } + + // Update index + std::string key = makeKey(protectionSpace); + auto it = credentials_index_.find(key); + if (it != credentials_index_.end()) { + auto& usernames = it->second; + usernames.erase( + std::remove(usernames.begin(), usernames.end(), credential.username), + usernames.end()); + if (usernames.empty()) { + credentials_index_.erase(it); + } + } + + saveIndex(); +} + +void CredentialDatabase::removeHttpAuthCredentials(const ProtectionSpace& protectionSpace) { + std::string key = makeKey(protectionSpace); + + auto it = credentials_index_.find(key); + if (it == credentials_index_.end()) { + return; + } + + // Remove all credentials for this protection space + for (const auto& username : it->second) { + GError* error = nullptr; + + secret_password_clear_sync( + getSchema(), + nullptr, + &error, + "appId", app_id_.c_str(), + "host", protectionSpace.host.c_str(), + "port", protectionSpace.port, + "protocol", protectionSpace.protocol.value_or("").c_str(), + "realm", protectionSpace.realm.value_or("").c_str(), + "username", username.c_str(), + NULL); + + if (error != nullptr) { + errorLog(std::string("CredentialDatabase: Failed to remove credential: ") + error->message); + g_error_free(error); + } + } + + credentials_index_.erase(it); + saveIndex(); +} + +void CredentialDatabase::clearAllAuthCredentials() { + // Remove all credentials from libsecret + for (const auto& [key, usernames] : credentials_index_) { + ProtectionSpace ps = fromKey(key); + + for (const auto& username : usernames) { + GError* error = nullptr; + + secret_password_clear_sync( + getSchema(), + nullptr, + &error, + "appId", app_id_.c_str(), + "host", ps.host.c_str(), + "port", ps.port, + "protocol", ps.protocol.value_or("").c_str(), + "realm", ps.realm.value_or("").c_str(), + "username", username.c_str(), + NULL); + + if (error != nullptr) { + g_error_free(error); + } + } + } + + credentials_index_.clear(); + + // Remove the index file + unlink(index_path_.c_str()); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/credential_database.h b/.vendor/flutter_inappwebview_linux/linux/credential_database.h new file mode 100644 index 00000000..4a0826e7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/credential_database.h @@ -0,0 +1,129 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CREDENTIAL_DATABASE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CREDENTIAL_DATABASE_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/** + * Represents a URL protection space for HTTP authentication. + */ +struct ProtectionSpace { + std::string host; + int port = 0; + std::optional protocol; + std::optional realm; + + ProtectionSpace() = default; + ProtectionSpace(FlValue* map); + + FlValue* toFlValue() const; + + // Comparison for map usage + bool operator==(const ProtectionSpace& other) const; +}; + +/** + * Represents a URL credential (username/password pair). + */ +struct Credential { + std::string username; + std::string password; + + Credential() = default; + Credential(const std::string& username, const std::string& password); + Credential(FlValue* map); + + FlValue* toFlValue() const; +}; + +/** + * Manages HTTP authentication credentials using libsecret for secure storage. + * + * Credentials are stored in the system keyring (gnome-keyring, KDE Wallet, etc.) + * using the Secret Service D-Bus API. + * + * Schema: com.pichillilorenzo.flutter_inappwebview.HttpAuth + * Attributes: appId, host, port, protocol, realm, username + */ +class CredentialDatabase : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_inappwebview_credential_database"; + + explicit CredentialDatabase(PluginInstance* plugin); + ~CredentialDatabase() override; + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + void HandleMethodCall(FlMethodCall* method_call) override; + + // Credential operations + std::vector>> getAllAuthCredentials(); + + std::vector getHttpAuthCredentials(const ProtectionSpace& protectionSpace); + + /** + * Lookup a single credential by username. + * Used for USE_SAVED_CREDENTIAL action. + */ + std::optional lookupCredential(const ProtectionSpace& protectionSpace, + const std::string& username); + + /** + * Lookup the first available credential for a protection space. + * Used when username is not specified. + */ + std::optional lookupFirstCredential(const ProtectionSpace& protectionSpace); + + void setHttpAuthCredential(const ProtectionSpace& protectionSpace, const Credential& credential); + + void removeHttpAuthCredential(const ProtectionSpace& protectionSpace, + const Credential& credential); + + void removeHttpAuthCredentials(const ProtectionSpace& protectionSpace); + + void clearAllAuthCredentials(); + + private: + PluginInstance* plugin_ = nullptr; + + // The libsecret schema for HTTP auth credentials + static const SecretSchema* getSchema(); + + // In-memory index of stored credentials (for getAllAuthCredentials) + // Key: "host:port:protocol:realm", Value: list of usernames + std::map> credentials_index_; + std::string index_path_; + + // Load/save the credential index (just usernames, not passwords) + void loadIndex(); + void saveIndex(); + static std::string getIndexPath(const std::string& app_id); + static std::string makeKey(const ProtectionSpace& ps); + + // Generate a human-readable label for the credential + static std::string makeLabel(const ProtectionSpace& ps, const std::string& username); + + // Parse key back to ProtectionSpace + static ProtectionSpace fromKey(const std::string& key); + + std::string app_id_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CREDENTIAL_DATABASE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.cc b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.cc new file mode 100644 index 00000000..07bc3066 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.cc @@ -0,0 +1,114 @@ +#include "find_interaction_channel_delegate.h" + +#include + +#include "find_interaction_controller.h" +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +FindInteractionChannelDelegate::FindInteractionChannelDelegate( + FindInteractionController* controller, FlBinaryMessenger* messenger, + const std::string& channelName) + : ChannelDelegate(messenger, channelName), + findInteractionController_(controller) {} + +FindInteractionChannelDelegate::~FindInteractionChannelDelegate() { + findInteractionController_ = nullptr; +} + +void FindInteractionChannelDelegate::HandleMethodCall( + FlMethodCall* method_call) { + const gchar* methodName = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (strcmp(methodName, "findAll") == 0) { + if (findInteractionController_) { + std::string find = get_fl_map_value(args, "find", ""); + if (!find.empty()) { + findInteractionController_->findAll(find); + } + } + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (strcmp(methodName, "findNext") == 0) { + if (findInteractionController_) { + bool forward = get_fl_map_value(args, "forward", true); + findInteractionController_->findNext(forward); + } + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (strcmp(methodName, "clearMatches") == 0) { + if (findInteractionController_) { + findInteractionController_->clearMatches(); + } + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (strcmp(methodName, "setSearchText") == 0) { + if (findInteractionController_) { + std::string searchText = + get_fl_map_value(args, "searchText", ""); + findInteractionController_->setSearchText(searchText); + } + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (strcmp(methodName, "getSearchText") == 0) { + if (findInteractionController_) { + auto searchText = findInteractionController_->getSearchText(); + if (searchText.has_value()) { + g_autoptr(FlValue) result = fl_value_new_string(searchText->c_str()); + fl_method_call_respond_success(method_call, result, nullptr); + } else { + fl_method_call_respond_success(method_call, nullptr, nullptr); + } + } else { + fl_method_call_respond_success(method_call, nullptr, nullptr); + } + return; + } + + if (strcmp(methodName, "getActiveFindSession") == 0) { + if (findInteractionController_) { + auto findSession = findInteractionController_->getActiveFindSession(); + if (findSession.has_value()) { + g_autoptr(FlValue) result = findSession->toFlValue(); + fl_method_call_respond_success(method_call, result, nullptr); + } else { + fl_method_call_respond_success(method_call, nullptr, nullptr); + } + } else { + fl_method_call_respond_success(method_call, nullptr, nullptr); + } + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void FindInteractionChannelDelegate::onFindResultReceived( + int32_t activeMatchOrdinal, int32_t numberOfMatches, + bool isDoneCounting) const { + if (channel_ == nullptr) return; + + g_autoptr(FlValue) args = to_fl_map({ + {"activeMatchOrdinal", make_fl_value(activeMatchOrdinal)}, + {"numberOfMatches", make_fl_value(numberOfMatches)}, + {"isDoneCounting", make_fl_value(isDoneCounting)}, + }); + + invokeMethod("onFindResultReceived", args); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.h b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.h new file mode 100644 index 00000000..3353d8e3 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_channel_delegate.h @@ -0,0 +1,34 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CHANNEL_DELEGATE_H_ + +#include + +#include +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class FindInteractionController; + +class FindInteractionChannelDelegate : public ChannelDelegate { + public: + FindInteractionChannelDelegate(FindInteractionController* controller, + FlBinaryMessenger* messenger, + const std::string& channelName); + ~FindInteractionChannelDelegate() override; + + void HandleMethodCall(FlMethodCall* method_call) override; + + void onFindResultReceived(int32_t activeMatchOrdinal, + int32_t numberOfMatches, + bool isDoneCounting) const; + + private: + FindInteractionController* findInteractionController_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CHANNEL_DELEGATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.cc b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.cc new file mode 100644 index 00000000..6e9010b0 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.cc @@ -0,0 +1,153 @@ +#include "find_interaction_controller.h" + +#include "find_interaction_channel_delegate.h" +#include "../in_app_webview/in_app_webview.h" +#include "../utils/log.h" + +namespace flutter_inappwebview_plugin { + +FindInteractionController::FindInteractionController(InAppWebView* webView) + : webView_(webView) { + // Connect to find controller signals + if (webView_ && webView_->webview()) { + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + if (find_controller) { + counted_matches_handler_id_ = g_signal_connect( + find_controller, "counted-matches", + G_CALLBACK(FindInteractionController::OnCountedMatches), this); + found_text_handler_id_ = + g_signal_connect(find_controller, "found-text", + G_CALLBACK(FindInteractionController::OnFoundText), this); + failed_to_find_text_handler_id_ = g_signal_connect( + find_controller, "failed-to-find-text", + G_CALLBACK(FindInteractionController::OnFailedToFindText), this); + } + } +} + +void FindInteractionController::attachChannel(FlBinaryMessenger* messenger, + const std::string& id) { + std::string channelName = std::string(METHOD_CHANNEL_NAME_PREFIX) + id; + channelDelegate_ = std::make_unique( + this, messenger, channelName); +} + +FindInteractionController::~FindInteractionController() { + dispose(); +} + +void FindInteractionController::findAll(const std::string& find) { + if (!webView_ || !webView_->webview()) return; + + searchText_ = find; + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + webkit_find_controller_search(find_controller, find.c_str(), + WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE, G_MAXUINT); +} + +void FindInteractionController::findNext(bool forward) { + if (!webView_ || !webView_->webview()) return; + + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + if (forward) { + webkit_find_controller_search_next(find_controller); + } else { + webkit_find_controller_search_previous(find_controller); + } +} + +void FindInteractionController::clearMatches() { + if (!webView_ || !webView_->webview()) return; + + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + webkit_find_controller_search_finish(find_controller); + activeFindSession_ = std::nullopt; +} + +void FindInteractionController::setSearchText(const std::string& searchText) { + searchText_ = searchText; + // Note: WebKit doesn't have a separate "set search text" API, + // the search text is set when calling search() +} + +std::optional FindInteractionController::getSearchText() const { + if (!webView_ || !webView_->webview()) return std::nullopt; + + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + const gchar* text = webkit_find_controller_get_search_text(find_controller); + if (text) { + return std::string(text); + } + return searchText_; +} + +std::optional FindInteractionController::getActiveFindSession() const { + return activeFindSession_; +} + +void FindInteractionController::OnCountedMatches( + WebKitFindController* find_controller, guint match_count, + gpointer user_data) { + if (!user_data) return; + auto* controller = static_cast(user_data); + if (controller->channelDelegate_) { + controller->activeFindSession_ = FindSession(static_cast(match_count), -1); + controller->channelDelegate_->onFindResultReceived(-1, static_cast(match_count), true); + } +} + +void FindInteractionController::OnFoundText( + WebKitFindController* find_controller, guint match_count, + gpointer user_data) { + if (!user_data) return; + auto* controller = static_cast(user_data); + if (controller->channelDelegate_) { + controller->channelDelegate_->onFindResultReceived(-1, static_cast(match_count), false); + } +} + +void FindInteractionController::OnFailedToFindText( + WebKitFindController* find_controller, gpointer user_data) { + if (!user_data) return; + auto* controller = static_cast(user_data); + if (controller->channelDelegate_) { + controller->activeFindSession_ = FindSession(0, 0); + controller->channelDelegate_->onFindResultReceived(0, 0, true); + } +} + +void FindInteractionController::dispose() { + if (webView_ && webView_->webview()) { + WebKitFindController* find_controller = + webkit_web_view_get_find_controller(webView_->webview()); + if (find_controller) { + if (counted_matches_handler_id_ > 0) { + g_signal_handler_disconnect(find_controller, counted_matches_handler_id_); + counted_matches_handler_id_ = 0; + } + if (found_text_handler_id_ > 0) { + g_signal_handler_disconnect(find_controller, found_text_handler_id_); + found_text_handler_id_ = 0; + } + if (failed_to_find_text_handler_id_ > 0) { + g_signal_handler_disconnect(find_controller, failed_to_find_text_handler_id_); + failed_to_find_text_handler_id_ = 0; + } + } + } + + if (channelDelegate_) { + channelDelegate_.reset(); + } + + webView_ = nullptr; + activeFindSession_ = std::nullopt; + searchText_ = std::nullopt; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.h b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.h new file mode 100644 index 00000000..816cd582 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/find_interaction/find_interaction_controller.h @@ -0,0 +1,59 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CONTROLLER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CONTROLLER_H_ + +#include +#include + +#include +#include +#include + +#include "../types/find_session.h" + +namespace flutter_inappwebview_plugin { + +class InAppWebView; +class FindInteractionChannelDelegate; + +class FindInteractionController { + public: + static constexpr const char* METHOD_CHANNEL_NAME_PREFIX = + "com.pichillilorenzo/flutter_inappwebview_find_interaction_"; + + FindInteractionController(InAppWebView* webView); + ~FindInteractionController(); + + // Attach the method channel with the given ID (called after texture_id is known) + void attachChannel(FlBinaryMessenger* messenger, const std::string& id); + + void findAll(const std::string& find); + void findNext(bool forward); + void clearMatches(); + void setSearchText(const std::string& searchText); + std::optional getSearchText() const; + std::optional getActiveFindSession() const; + + void dispose(); + + // Signal handlers + static void OnCountedMatches(WebKitFindController* find_controller, + guint match_count, gpointer user_data); + static void OnFoundText(WebKitFindController* find_controller, + guint match_count, gpointer user_data); + static void OnFailedToFindText(WebKitFindController* find_controller, + gpointer user_data); + + InAppWebView* webView_; + std::unique_ptr channelDelegate_; + std::optional searchText_; + std::optional activeFindSession_; + + private: + gulong counted_matches_handler_id_ = 0; + gulong found_text_handler_id_ = 0; + gulong failed_to_find_text_handler_id_ = 0; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_INTERACTION_CONTROLLER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin.cc b/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin.cc new file mode 100644 index 00000000..b6c9cc5a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin.cc @@ -0,0 +1,182 @@ +#include "include/flutter_inappwebview_linux/flutter_inappwebview_linux_plugin.h" + +#include "flutter_inappwebview_linux_plugin_private.h" +#include "plugin_instance.h" + +#include +#include +#include + +#include +#include + +#include "cookie_manager.h" +#include "credential_database.h" +#include "headless_in_app_webview/headless_in_app_webview_manager.h" +#include "in_app_browser/in_app_browser_manager.h" +#include "in_app_webview/in_app_webview_manager.h" +#include "proxy_manager.h" +#include "utils/software_rendering.h" +#include "web_storage_manager.h" +#include "webview_environment.h" + +#define FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_inappwebview_linux_plugin_get_type(), \ + FlutterInappwebviewLinuxPlugin)) + +struct _FlutterInappwebviewLinuxPlugin { + GObject parent_instance; + FlPluginRegistrar* registrar; + + // C++ plugin instance for passing to managers + std::unique_ptr plugin_instance; + + // Managers are owned by the GObject + std::unique_ptr in_app_webview_manager; + std::unique_ptr headless_in_app_webview_manager; + std::unique_ptr in_app_browser_manager; + std::unique_ptr cookie_manager; + std::unique_ptr credential_database; + std::unique_ptr proxy_manager; + std::unique_ptr web_storage_manager; + std::unique_ptr webview_environment; +}; + +G_DEFINE_TYPE(FlutterInappwebviewLinuxPlugin, flutter_inappwebview_linux_plugin, + g_object_get_type()) + +static void flutter_inappwebview_linux_plugin_dispose(GObject* object) { + FlutterInappwebviewLinuxPlugin* self = FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN(object); + + // Clean up the managers + self->in_app_webview_manager.reset(); + self->headless_in_app_webview_manager.reset(); + self->in_app_browser_manager.reset(); + self->cookie_manager.reset(); + self->credential_database.reset(); + self->proxy_manager.reset(); + self->web_storage_manager.reset(); + self->webview_environment.reset(); + + // Clean up the plugin instance last (after managers are gone) + self->plugin_instance.reset(); + + G_OBJECT_CLASS(flutter_inappwebview_linux_plugin_parent_class)->dispose(object); +} + +static void flutter_inappwebview_linux_plugin_class_init( + FlutterInappwebviewLinuxPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = flutter_inappwebview_linux_plugin_dispose; +} + +static void flutter_inappwebview_linux_plugin_init(FlutterInappwebviewLinuxPlugin* self) { + self->registrar = nullptr; + // Note: in_app_webview_manager is initialized by its default constructor +} + +void flutter_inappwebview_linux_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + // === CRITICAL: Check for VM/problematic GPU BEFORE any WebKit initialization === + // This must happen before any WPEDisplay is created, because WebKit's WebProcess + // inherits environment variables at spawn time. Setting LIBGL_ALWAYS_SOFTWARE + // after WebProcess starts has no effect. + flutter_inappwebview_plugin::ApplySoftwareRenderingIfNeeded(); + + FlutterInappwebviewLinuxPlugin* plugin = FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN( + g_object_new(flutter_inappwebview_linux_plugin_get_type(), nullptr)); + + plugin->registrar = registrar; + + plugin->plugin_instance = std::make_unique(registrar); + auto* pluginInstance = plugin->plugin_instance.get(); + + plugin->in_app_webview_manager = + std::make_unique(pluginInstance); + pluginInstance->inAppWebViewManager = plugin->in_app_webview_manager.get(); + + plugin->headless_in_app_webview_manager = + std::make_unique(pluginInstance); + pluginInstance->headlessInAppWebViewManager = plugin->headless_in_app_webview_manager.get(); + + plugin->in_app_browser_manager = + std::make_unique(pluginInstance); + pluginInstance->inAppBrowserManager = plugin->in_app_browser_manager.get(); + + plugin->cookie_manager = std::make_unique(pluginInstance); + pluginInstance->cookieManager = plugin->cookie_manager.get(); + + plugin->credential_database = + std::make_unique(pluginInstance); + pluginInstance->credentialDatabase = plugin->credential_database.get(); + + plugin->proxy_manager = std::make_unique(pluginInstance); + pluginInstance->proxyManager = plugin->proxy_manager.get(); + + plugin->web_storage_manager = std::make_unique(pluginInstance); + pluginInstance->webStorageManager = plugin->web_storage_manager.get(); + + plugin->webview_environment = std::make_unique(pluginInstance); + pluginInstance->webViewEnvironment = plugin->webview_environment.get(); + + // Note: We don't unref the plugin here as it needs to stay alive + // for the lifetime of the application + g_object_ref(plugin); +} + +// === Helper functions for accessing Flutter view and window === + +FlView* flutter_inappwebview_linux_plugin_get_view(FlPluginRegistrar* registrar) { + if (registrar == nullptr) { + return nullptr; + } + return fl_plugin_registrar_get_view(registrar); +} + +GtkWindow* flutter_inappwebview_linux_plugin_get_window(FlPluginRegistrar* registrar) { + FlView* view = flutter_inappwebview_linux_plugin_get_view(registrar); + if (view == nullptr) { + return nullptr; + } + + GtkWidget* widget = GTK_WIDGET(view); + if (widget == nullptr) { + return nullptr; + } + + GtkWidget* toplevel = gtk_widget_get_toplevel(widget); + if (toplevel == nullptr || !GTK_IS_WINDOW(toplevel)) { + return nullptr; + } + + return GTK_WINDOW(toplevel); +} + +int flutter_inappwebview_linux_plugin_get_monitor_refresh_rate(FlPluginRegistrar* registrar) { + GtkWindow* window = flutter_inappwebview_linux_plugin_get_window(registrar); + return flutter_inappwebview_linux_plugin_get_monitor_refresh_rate_for_window(window); +} + +int flutter_inappwebview_linux_plugin_get_monitor_refresh_rate_for_window(GtkWindow* window) { + if (window == nullptr) { + return 0; + } + + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); + if (gdk_window == nullptr) { + return 0; + } + + GdkDisplay* display = gdk_display_get_default(); + if (display == nullptr) { + return 0; + } + + GdkMonitor* monitor = gdk_display_get_monitor_at_window(display, gdk_window); + if (monitor == nullptr) { + return 0; + } + + // gdk_monitor_get_refresh_rate returns the refresh rate in millihertz + // (e.g., 60000 for 60 Hz) + return gdk_monitor_get_refresh_rate(monitor); +} + diff --git a/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin_private.h b/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin_private.h new file mode 100644 index 00000000..e7b792b5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/flutter_inappwebview_linux_plugin_private.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN_PRIVATE_H_ +#define FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN_PRIVATE_H_ + +#include +#include + +#include + +G_BEGIN_DECLS + +// Get the FlView from the plugin registrar +// Returns nullptr if not available +FlView* flutter_inappwebview_linux_plugin_get_view(FlPluginRegistrar* registrar); + +// Get the GtkWindow from the plugin registrar (via FlView) +// Returns nullptr if not available +GtkWindow* flutter_inappwebview_linux_plugin_get_window(FlPluginRegistrar* registrar); + +// Get the monitor refresh rate in millihertz for the window +// Returns 0 if not available or unknown +int flutter_inappwebview_linux_plugin_get_monitor_refresh_rate(FlPluginRegistrar* registrar); + +// Get the monitor refresh rate in millihertz for the given GtkWindow +// Returns 0 if not available or unknown +int flutter_inappwebview_linux_plugin_get_monitor_refresh_rate_for_window(GtkWindow* window); + +G_END_DECLS + +#endif // FLUTTER_INAPPWEBVIEW_LINUX_PLUGIN_PRIVATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.cc b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.cc new file mode 100644 index 00000000..a5fcc4ff --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.cc @@ -0,0 +1,65 @@ +#include "headless_in_app_webview.h" + +#include + +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "headless_in_app_webview_manager.h" +#include "headless_webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +HeadlessInAppWebView::HeadlessInAppWebView(HeadlessInAppWebViewManager* manager, + const HeadlessInAppWebViewCreationParams& params, + const InAppWebViewCreationParams& webviewParams) + : manager_(manager), id_(params.id), width_(params.initialWidth), height_(params.initialHeight) { + debugLog("HeadlessInAppWebView::HeadlessInAppWebView id=" + id_); + + // Create the underlying InAppWebView + // Use 0 as the numeric ID since we use string ID for headless webviews + webview_ = std::make_shared(manager_->registrar(), manager_->messenger(), 0, + webviewParams); + + // CRITICAL: Attach the method channel to the InAppWebView using the string ID. + // This creates the channel at "com.pichillilorenzo/flutter_inappwebview_" + // which the Dart LinuxInAppWebViewController expects. + webview_->AttachChannel(manager_->messenger(), id_, false); + + // Set the initial size + webview_->setSize(static_cast(width_), static_cast(height_)); + + // Create the channel delegate for this headless webview + // This handles headless-specific methods like setSize, getSize, dispose + channelDelegate_ = std::make_unique( + this, manager_->messenger(), id_); +} + +HeadlessInAppWebView::~HeadlessInAppWebView() { + debugLog("HeadlessInAppWebView::~HeadlessInAppWebView id=" + id_); + + channelDelegate_.reset(); + webview_.reset(); +} + +void HeadlessInAppWebView::setSize(double width, double height) { + width_ = width; + height_ = height; + if (webview_) { + webview_->setSize(static_cast(width_), static_cast(height_)); + } +} + +void HeadlessInAppWebView::getSize(double* width, double* height) const { + if (width) *width = width_; + if (height) *height = height_; +} + +void HeadlessInAppWebView::dispose() { + // Remove this headless webview from the manager + // This will trigger the destructor + if (manager_) { + manager_->RemoveHeadlessWebView(id_); + } +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.h b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.h new file mode 100644 index 00000000..d63010c9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview.h @@ -0,0 +1,64 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ + +#include + +#include +#include + +#include "../in_app_webview/in_app_webview.h" +#include "headless_webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class HeadlessInAppWebViewManager; + +struct HeadlessInAppWebViewCreationParams { + std::string id; + double initialWidth = -1; + double initialHeight = -1; +}; + +/// HeadlessInAppWebView - A WebView without visual output +/// +/// This class wraps InAppWebView for headless operation. +/// Since WPE WebKit is inherently headless (it renders to offscreen buffers), +/// this is essentially InAppWebView without texture registration. +class HeadlessInAppWebView { + public: + + HeadlessInAppWebView(HeadlessInAppWebViewManager* manager, + const HeadlessInAppWebViewCreationParams& params, + const InAppWebViewCreationParams& webviewParams); + ~HeadlessInAppWebView(); + + const std::string& id() const { return id_; } + InAppWebView* webview() const { return webview_.get(); } + HeadlessInAppWebViewManager* manager() const { return manager_; } + + // Size management + void setSize(double width, double height); + void getSize(double* width, double* height) const; + + // Get the channel delegate + HeadlessWebViewChannelDelegate* channelDelegate() const { return channelDelegate_.get(); } + + // Dispose this headless webview (called by channel delegate) + void dispose(); + + private: + HeadlessInAppWebViewManager* manager_ = nullptr; + std::string id_; + double width_ = -1; + double height_ = -1; + + // The underlying InAppWebView + std::shared_ptr webview_; + + // Channel delegate for this headless webview + std::unique_ptr channelDelegate_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.cc b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.cc new file mode 100644 index 00000000..b72967ca --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.cc @@ -0,0 +1,190 @@ +#include "headless_in_app_webview_manager.h" + +#include + +#include "../flutter_inappwebview_linux_plugin_private.h" +#include "../plugin_instance.h" +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../webview_environment.h" + +namespace flutter_inappwebview_plugin { + +HeadlessInAppWebViewManager::HeadlessInAppWebViewManager(PluginInstance* plugin) + : plugin_(plugin), registrar_(plugin->registrar()) { + messenger_ = plugin->messenger(); + + // Cache the GTK window from the plugin instance + // This may return nullptr for headless scenarios, which is fine + gtk_window_ = plugin->gtkWindow(); + + // Create the method channel + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + method_channel_ = fl_method_channel_new(messenger_, METHOD_CHANNEL_NAME, FL_METHOD_CODEC(codec)); + + fl_method_channel_set_method_call_handler(method_channel_, HandleMethodCall, this, nullptr); +} + +FlPluginRegistrar* HeadlessInAppWebViewManager::registrar() const { + return registrar_; +} + +HeadlessInAppWebViewManager::~HeadlessInAppWebViewManager() { + debugLog("dealloc HeadlessInAppWebViewManager"); + + // Clean up all headless webviews + webviews_.clear(); + + if (method_channel_ != nullptr) { + fl_method_channel_set_method_call_handler(method_channel_, nullptr, nullptr, nullptr); + g_object_unref(method_channel_); + method_channel_ = nullptr; + } +} + +HeadlessInAppWebView* HeadlessInAppWebViewManager::GetHeadlessWebView( + const std::string& id) const { + auto it = webviews_.find(id); + if (it != webviews_.end()) { + return it->second.get(); + } + return nullptr; +} + +void HeadlessInAppWebViewManager::RemoveHeadlessWebView(const std::string& id) { + auto it = webviews_.find(id); + if (it != webviews_.end()) { + webviews_.erase(it); + } +} + +void HeadlessInAppWebViewManager::HandleMethodCall(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + auto* self = static_cast(user_data); + self->HandleMethodCallImpl(method_call); +} + +void HeadlessInAppWebViewManager::HandleMethodCallImpl(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "run") == 0) { + Run(method_call); + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void HeadlessInAppWebViewManager::Run(FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + fl_method_call_respond_error(method_call, "INVALID_ARGUMENTS", "Arguments must be a map", + nullptr, nullptr); + return; + } + + // Get the ID from Flutter + auto idOpt = get_optional_fl_map_value(args, "id"); + if (!idOpt.has_value()) { + fl_method_call_respond_error(method_call, "INVALID_ARGUMENTS", "Missing id parameter", nullptr, + nullptr); + return; + } + std::string id = idOpt.value(); + + // Get the params map + FlValue* params = get_fl_map_value_raw(args, "params"); + if (params == nullptr || fl_value_get_type(params) != FL_VALUE_TYPE_MAP) { + fl_method_call_respond_error(method_call, "INVALID_ARGUMENTS", "Missing params parameter", + nullptr, nullptr); + return; + } + + // Parse initial size + double initialWidth = -1; + double initialHeight = -1; + FlValue* initial_size = get_fl_map_value_raw(params, "initialSize"); + if (initial_size != nullptr && fl_value_get_type(initial_size) == FL_VALUE_TYPE_MAP) { + initialWidth = get_fl_map_value(initial_size, "width", -1); + initialHeight = get_fl_map_value(initial_size, "height", -1); + } + + // Create headless params + HeadlessInAppWebViewCreationParams headlessParams; + headlessParams.id = id; + headlessParams.initialWidth = initialWidth >= 0 ? initialWidth : 800; + headlessParams.initialHeight = initialHeight >= 0 ? initialHeight : 600; + + // Create InAppWebView params + InAppWebViewCreationParams webviewParams; + webviewParams.id = 0; // Not used for headless + webviewParams.plugin = plugin_; // Pass plugin instance for accessing managers + webviewParams.gtkWindow = gtk_window_; // Use cached GTK window (may be nullptr for headless) + webviewParams.manager = nullptr; // Not needed for headless + + // Parse initial settings + FlValue* initial_settings = get_fl_map_value_raw(params, "initialSettings"); + if (initial_settings != nullptr && fl_value_get_type(initial_settings) == FL_VALUE_TYPE_MAP) { + webviewParams.initialSettings = std::make_shared(initial_settings); + } else { + webviewParams.initialSettings = std::make_shared(); + } + + // Parse initial URL request + FlValue* initial_url_request = get_fl_map_value_raw(params, "initialUrlRequest"); + if (initial_url_request != nullptr && + fl_value_get_type(initial_url_request) == FL_VALUE_TYPE_MAP) { + webviewParams.initialUrlRequest = std::make_shared(initial_url_request); + } + + // Parse initial data + FlValue* initial_data = get_fl_map_value_raw(params, "initialData"); + if (initial_data != nullptr && fl_value_get_type(initial_data) == FL_VALUE_TYPE_MAP) { + webviewParams.initialData = get_fl_map_value(initial_data, "data", ""); + webviewParams.initialDataBaseUrl = get_fl_map_value(initial_data, "baseUrl", ""); + webviewParams.initialDataMimeType = get_fl_map_value(initial_data, "mimeType", ""); + webviewParams.initialDataEncoding = get_fl_map_value(initial_data, "encoding", ""); + } + + // Parse initial file + auto initial_file = get_optional_fl_map_value(params, "initialFile"); + if (initial_file.has_value()) { + webviewParams.initialFile = initial_file.value(); + } + + // Parse webViewEnvironmentId and look up the custom WebKitWebContext + auto webViewEnvironmentIdOpt = get_optional_fl_map_value(params, "webViewEnvironmentId"); + if (webViewEnvironmentIdOpt.has_value() && !webViewEnvironmentIdOpt->empty()) { + WebViewEnvironment* webViewEnv = plugin_ ? plugin_->webViewEnvironment : nullptr; + WebKitWebContext* webContext = nullptr; + if (webViewEnv != nullptr) { + webContext = webViewEnv->getWebContext(webViewEnvironmentIdOpt.value()); + } + if (webContext != nullptr) { + webviewParams.webContext = webContext; + debugLog("HeadlessInAppWebViewManager: Using custom WebKitWebContext from WebViewEnvironment id=" + webViewEnvironmentIdOpt.value()); + } else { + debugLog("HeadlessInAppWebViewManager: WebViewEnvironment not found for id=" + webViewEnvironmentIdOpt.value()); + } + } + + // Create the headless webview + auto headlessWebView = + std::make_unique(this, headlessParams, webviewParams); + + // Store it + webviews_[id] = std::move(headlessWebView); + + // Notify Flutter that the webview is created + webviews_[id]->channelDelegate()->onWebViewCreated(); + + // Return success + g_autoptr(FlValue) result = make_fl_value(true); + fl_method_call_respond_success(method_call, result, nullptr); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.h b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.h new file mode 100644 index 00000000..697cb4b5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_in_app_webview_manager.h @@ -0,0 +1,53 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_ + +#include + +#include +#include +#include + +#include "headless_in_app_webview.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +class HeadlessInAppWebViewManager { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_headless_inappwebview"; + + HeadlessInAppWebViewManager(PluginInstance* plugin); + ~HeadlessInAppWebViewManager(); + + PluginInstance* plugin() const { return plugin_; } + FlPluginRegistrar* registrar() const; + FlBinaryMessenger* messenger() const { return messenger_; } + + // Get a headless webview by ID + HeadlessInAppWebView* GetHeadlessWebView(const std::string& id) const; + + // Remove a headless webview by ID (called during dispose) + void RemoveHeadlessWebView(const std::string& id); + + private: + PluginInstance* plugin_ = nullptr; + FlPluginRegistrar* registrar_ = nullptr; + FlBinaryMessenger* messenger_ = nullptr; + FlMethodChannel* method_channel_ = nullptr; + GtkWindow* gtk_window_ = nullptr; // Cached GTK window (can be null for headless) + + // Map of id to HeadlessInAppWebView instance + std::map> webviews_; + + // Handle method calls from Flutter + static void HandleMethodCall(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data); + void HandleMethodCallImpl(FlMethodCall* method_call); + void Run(FlMethodCall* method_call); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.cc b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.cc new file mode 100644 index 00000000..ff2ae96a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.cc @@ -0,0 +1,85 @@ +#include "headless_webview_channel_delegate.h" + +#include + +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "headless_in_app_webview.h" + +namespace flutter_inappwebview_plugin { + +HeadlessWebViewChannelDelegate::HeadlessWebViewChannelDelegate( + HeadlessInAppWebView* headlessWebView, + FlBinaryMessenger* messenger, + const std::string& id) + : ChannelDelegate(messenger, std::string(METHOD_CHANNEL_NAME_PREFIX) + id), + headlessWebView_(headlessWebView) { +} + +HeadlessWebViewChannelDelegate::~HeadlessWebViewChannelDelegate() { + debugLog("dealloc HeadlessWebViewChannelDelegate"); + headlessWebView_ = nullptr; +} + +void HeadlessWebViewChannelDelegate::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "dispose") == 0) { + if (headlessWebView_ != nullptr) { + headlessWebView_->dispose(); + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + } else { + g_autoptr(FlValue) result = fl_value_new_bool(false); + fl_method_call_respond_success(method_call, result, nullptr); + } + return; + } + + if (strcmp(method, "setSize") == 0) { + if (headlessWebView_ != nullptr) { + FlValue* args = fl_method_call_get_args(method_call); + if (args != nullptr && fl_value_get_type(args) == FL_VALUE_TYPE_MAP) { + FlValue* size_value = fl_value_lookup_string(args, "size"); + if (size_value != nullptr && fl_value_get_type(size_value) == FL_VALUE_TYPE_MAP) { + double width = get_fl_map_value(size_value, "width", -1); + double height = get_fl_map_value(size_value, "height", -1); + if (width >= 0 && height >= 0) { + headlessWebView_->setSize(width, height); + } + } + } + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + } else { + g_autoptr(FlValue) result = fl_value_new_bool(false); + fl_method_call_respond_success(method_call, result, nullptr); + } + return; + } + + if (strcmp(method, "getSize") == 0) { + if (headlessWebView_ != nullptr) { + double width, height; + headlessWebView_->getSize(&width, &height); + g_autoptr(FlValue) result = + to_fl_map({{"width", make_fl_value(width)}, {"height", make_fl_value(height)}}); + fl_method_call_respond_success(method_call, result, nullptr); + } else { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + } + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void HeadlessWebViewChannelDelegate::onWebViewCreated() const { + if (channel_ == nullptr) { + return; + } + g_autoptr(FlValue) args = to_fl_map({}); + invokeMethod("onWebViewCreated", args); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.h b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.h new file mode 100644 index 00000000..7298f403 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/headless_in_app_webview/headless_webview_channel_delegate.h @@ -0,0 +1,45 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ + +#include + +#include +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class HeadlessInAppWebView; + +/** + * Channel delegate for HeadlessInAppWebView. + * Handles method calls specific to headless webview operations (dispose, setSize, getSize). + * This mirrors the iOS HeadlessWebViewChannelDelegate pattern. + */ +class HeadlessWebViewChannelDelegate : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME_PREFIX = + "com.pichillilorenzo/flutter_headless_inappwebview_"; + + HeadlessWebViewChannelDelegate(HeadlessInAppWebView* headlessWebView, + FlBinaryMessenger* messenger, + const std::string& id); + ~HeadlessWebViewChannelDelegate() override; + + void HandleMethodCall(FlMethodCall* method_call) override; + + // === Events to send to Dart === + + /** + * Notify Dart that the headless webview has been created. + */ + void onWebViewCreated() const; + + private: + HeadlessInAppWebView* headlessWebView_ = nullptr; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.cc new file mode 100644 index 00000000..70c71eda --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.cc @@ -0,0 +1,1417 @@ +#include "in_app_browser.h" + +#include +#include +#include + +#include "../plugin_instance.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../webview_environment.h" +#include "in_app_browser_manager.h" + +namespace flutter_inappwebview_plugin { + +// Convert color from #AARRGGBB (Flutter format) to rgba(r,g,b,a) (GTK CSS format) +// Flutter uses #AARRGGBB, but GTK CSS expects #RRGGBB, #RRGGBBAA, or rgba() +static std::string ConvertColorToGtkCss(const std::string& color) { + if (color.empty()) { + return color; + } + + // Handle #AARRGGBB format (9 chars including #) + if (color.length() == 9 && color[0] == '#') { + // Extract components: #AARRGGBB + std::string aa = color.substr(1, 2); + std::string rr = color.substr(3, 2); + std::string gg = color.substr(5, 2); + std::string bb = color.substr(7, 2); + + // Parse hex values + int a = std::stoi(aa, nullptr, 16); + int r = std::stoi(rr, nullptr, 16); + int g = std::stoi(gg, nullptr, 16); + int b = std::stoi(bb, nullptr, 16); + + // Return rgba() format + char buffer[64]; + snprintf(buffer, sizeof(buffer), "rgba(%d, %d, %d, %.3f)", r, g, b, a / 255.0); + return std::string(buffer); + } + + // Return as-is for other formats (#RGB, #RRGGBB, rgb(), rgba(), etc.) + return color; +} + +// InAppBrowserMenuItem implementation + +InAppBrowserMenuItem::InAppBrowserMenuItem(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + id = get_fl_map_value(map, "id", 0); + title = get_fl_map_value(map, "title", ""); + order = get_fl_map_value(map, "order", 0); +} + +// InAppBrowser implementation + +InAppBrowser::InAppBrowser(InAppBrowserManager* manager, FlBinaryMessenger* messenger, + GtkWindow* parentWindow, const InAppBrowserCreationParams& params) + : plugin_(params.plugin), + manager_(manager), + messenger_(messenger), + parentWindow_(parentWindow), + id_(params.id), + settings_(params.initialSettings), + menuItems_(params.menuItems) { + // Validate messenger + if (messenger_ == nullptr || !FL_IS_BINARY_MESSENGER(messenger_)) { + errorLog("InAppBrowser: Invalid messenger"); + return; + } + + // Create channel delegate + std::string channelName = std::string(METHOD_CHANNEL_NAME_PREFIX) + id_; + channelDelegate_ = std::make_unique(this, messenger_, channelName); + + // Setup the window and components + setupWindow(params); + setupToolbar(); + setupDrawingArea(); + setupWebView(params); + applySettings(); + + // Show window unless hidden + if (!settings_->hidden) { + gtk_widget_show_all(GTK_WIDGET(window_)); + // Apply toolbar visibility after show_all + if (settings_->hideToolbarTop) { + gtk_widget_hide(headerBar_); + } + } + + // Load initial content (async, WebView needs time to initialize) + g_idle_add( + [](gpointer user_data) -> gboolean { + auto* browser = static_cast(user_data); + if (browser && !browser->destroyed_) { + // Notify Dart that browser is created + if (browser->channelDelegate_) { + browser->channelDelegate_->onBrowserCreated(); + } + } + return G_SOURCE_REMOVE; + }, + this); +} + +InAppBrowser::~InAppBrowser() { + debugLog("dealloc InAppBrowser"); + cleanup(); +} + +void InAppBrowser::cleanup() { + if (destroyed_) { + return; + } + destroyed_ = true; + + // Cancel frame callback + if (frameSourceId_ != 0) { + g_source_remove(frameSourceId_); + frameSourceId_ = 0; + } + + // Clean up cursor + if (currentCursor_ != nullptr) { + g_object_unref(currentCursor_); + currentCursor_ = nullptr; + } + + // Clean up WebView + webView_.reset(); + + // Clean up channel delegate + if (channelDelegate_) { + channelDelegate_->unregisterMethodCallHandler(); + channelDelegate_.reset(); + } + + // Nullify window reference (GTK handles cleanup via destroy signal) + window_ = nullptr; + headerBar_ = nullptr; + backButton_ = nullptr; + forwardButton_ = nullptr; + reloadButton_ = nullptr; + urlEntry_ = nullptr; + progressBar_ = nullptr; + menuButton_ = nullptr; + contentBox_ = nullptr; + drawingArea_ = nullptr; + + // Clean up GL resources + if (glArea_ != nullptr && gtk_widget_get_realized(glArea_)) { + gtk_gl_area_make_current(GTK_GL_AREA(glArea_)); + if (glTexture_ != 0) { + glDeleteTextures(1, &glTexture_); + glTexture_ = 0; + } + if (glVBO_ != 0) { + glDeleteBuffers(1, &glVBO_); + glVBO_ = 0; + } + if (glProgram_ != 0) { + glDeleteProgram(glProgram_); + glProgram_ = 0; + } + } + glArea_ = nullptr; + glInitialized_ = false; + glAttribPosition_ = -1; + glAttribTexture_ = -1; + glUniformTexture_ = -1; + + manager_ = nullptr; + messenger_ = nullptr; + parentWindow_ = nullptr; + plugin_ = nullptr; +} + +void InAppBrowser::setupWindow(const InAppBrowserCreationParams& params) { + // Create top-level window + window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + + // Set window title + if (!settings_->toolbarTopFixedTitle.empty()) { + gtk_window_set_title(window_, settings_->toolbarTopFixedTitle.c_str()); + } else { + gtk_window_set_title(window_, "InAppBrowser"); + } + + // Set window size + int width = 1280; + int height = 720; + if (settings_->windowFrame) { + width = static_cast(settings_->windowFrame->width); + height = static_cast(settings_->windowFrame->height); + } + gtk_window_set_default_size(window_, width, height); + + // Set window position + if (settings_->windowFrame) { + gtk_window_move(window_, static_cast(settings_->windowFrame->x), + static_cast(settings_->windowFrame->y)); + } + + // Set window type hints + if (settings_->windowType == InAppBrowserWindowType::child) { + if (parentWindow_ != nullptr) { + gtk_window_set_transient_for(window_, parentWindow_); + gtk_window_set_destroy_with_parent(window_, TRUE); + } + } + + // Set opacity + gtk_widget_set_opacity(GTK_WIDGET(window_), settings_->windowAlphaValue); + + // Create main vertical box + contentBox_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(window_), contentBox_); + + // Connect signals + g_signal_connect(window_, "destroy", G_CALLBACK(OnWindowDestroy), this); + g_signal_connect(window_, "delete-event", G_CALLBACK(OnWindowDeleteEvent), this); +} + +void InAppBrowser::setupToolbar() { + // Create header bar + headerBar_ = gtk_header_bar_new(); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerBar_), TRUE); + + // Apply background color if specified + if (!settings_->toolbarTopBackgroundColor.empty()) { + GtkCssProvider* provider = gtk_css_provider_new(); + std::string gtkColor = ConvertColorToGtkCss(settings_->toolbarTopBackgroundColor); + std::string css = "headerbar { background-color: " + gtkColor + "; }"; + gtk_css_provider_load_from_data(provider, css.c_str(), -1, nullptr); + GtkStyleContext* context = gtk_widget_get_style_context(headerBar_); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref(provider); + } + + // Create navigation buttons (unless hidden) + if (!settings_->hideDefaultMenuItems) { + // Navigation button box + GtkWidget* navBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_style_context_add_class(gtk_widget_get_style_context(navBox), "linked"); + + // Back button + backButton_ = gtk_button_new_from_icon_name("go-previous-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(backButton_, "Go Back"); + gtk_widget_set_sensitive(backButton_, FALSE); + g_signal_connect(backButton_, "clicked", G_CALLBACK(OnBackClicked), this); + gtk_box_pack_start(GTK_BOX(navBox), backButton_, FALSE, FALSE, 0); + + // Forward button + forwardButton_ = gtk_button_new_from_icon_name("go-next-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(forwardButton_, "Go Forward"); + gtk_widget_set_sensitive(forwardButton_, FALSE); + g_signal_connect(forwardButton_, "clicked", G_CALLBACK(OnForwardClicked), this); + gtk_box_pack_start(GTK_BOX(navBox), forwardButton_, FALSE, FALSE, 0); + + // Reload button + reloadButton_ = gtk_button_new_from_icon_name("view-refresh-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(reloadButton_, "Reload"); + g_signal_connect(reloadButton_, "clicked", G_CALLBACK(OnReloadClicked), this); + gtk_box_pack_start(GTK_BOX(navBox), reloadButton_, FALSE, FALSE, 0); + + gtk_header_bar_pack_start(GTK_HEADER_BAR(headerBar_), navBox); + } + + // Create URL entry (unless hidden) + if (!settings_->hideUrlBar) { + urlEntry_ = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(urlEntry_), "Enter URL..."); + gtk_widget_set_hexpand(urlEntry_, TRUE); + gtk_entry_set_icon_from_icon_name(GTK_ENTRY(urlEntry_), GTK_ENTRY_ICON_PRIMARY, "globe-symbolic"); + g_signal_connect(urlEntry_, "activate", G_CALLBACK(OnUrlEntryActivated), this); + gtk_header_bar_set_custom_title(GTK_HEADER_BAR(headerBar_), urlEntry_); + } + + // Create menu button if there are menu items + if (!menuItems_.empty()) { + menuButton_ = gtk_menu_button_new(); + gtk_button_set_image(GTK_BUTTON(menuButton_), + gtk_image_new_from_icon_name("open-menu-symbolic", GTK_ICON_SIZE_BUTTON)); + gtk_widget_set_tooltip_text(menuButton_, "Menu"); + + // Create menu + GtkWidget* menu = gtk_menu_new(); + for (const auto& item : menuItems_) { + GtkWidget* menuItem = gtk_menu_item_new_with_label(item.title.c_str()); + g_object_set_data(G_OBJECT(menuItem), "menu-item-id", GINT_TO_POINTER(item.id)); + g_signal_connect(menuItem, "activate", G_CALLBACK(OnMenuItemActivated), this); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuItem); + } + gtk_widget_show_all(menu); + gtk_menu_button_set_popup(GTK_MENU_BUTTON(menuButton_), menu); + + gtk_header_bar_pack_end(GTK_HEADER_BAR(headerBar_), menuButton_); + } + + // Use the header bar as the title bar + gtk_window_set_titlebar(window_, headerBar_); + + // Create progress bar (in content area, below header) + if (!settings_->hideProgressBar) { + progressBar_ = gtk_progress_bar_new(); + gtk_widget_set_valign(progressBar_, GTK_ALIGN_START); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressBar_), 0.0); + gtk_box_pack_start(GTK_BOX(contentBox_), progressBar_, FALSE, FALSE, 0); + + // Style the progress bar to be thin + GtkCssProvider* provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data( + provider, + "progressbar { min-height: 3px; } " + "progressbar trough { min-height: 3px; } " + "progressbar progress { min-height: 3px; }", + -1, nullptr); + GtkStyleContext* context = gtk_widget_get_style_context(progressBar_); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref(provider); + } +} + +void InAppBrowser::setupDrawingArea() { + // Try to use GtkGLArea for hardware-accelerated zero-copy rendering + // This provides better performance by avoiding GPU->CPU->GPU copies + bool canUseGl = InAppWebView::IsWpeWebKitAvailable(); + + if (canUseGl) { + // Create GtkGLArea for zero-copy EGL texture rendering + glArea_ = gtk_gl_area_new(); + gtk_widget_set_can_focus(glArea_, TRUE); + gtk_widget_set_hexpand(glArea_, TRUE); + gtk_widget_set_vexpand(glArea_, TRUE); + + // Use OpenGL ES for compatibility with WPE's EGL images + gtk_gl_area_set_use_es(GTK_GL_AREA(glArea_), TRUE); + + // Enable events + gtk_widget_add_events(glArea_, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | + GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + + // Connect GL-specific signals + g_signal_connect(glArea_, "realize", G_CALLBACK(OnGlAreaRealize), this); + g_signal_connect(glArea_, "render", G_CALLBACK(OnGlAreaRender), this); + g_signal_connect(glArea_, "resize", G_CALLBACK(OnGlAreaResize), this); + + // Connect input signals (same as drawing area) + g_signal_connect(glArea_, "button-press-event", G_CALLBACK(OnDrawingAreaButtonPress), this); + g_signal_connect(glArea_, "button-release-event", G_CALLBACK(OnDrawingAreaButtonRelease), this); + g_signal_connect(glArea_, "motion-notify-event", G_CALLBACK(OnDrawingAreaMotionNotify), this); + g_signal_connect(glArea_, "scroll-event", G_CALLBACK(OnDrawingAreaScroll), this); + g_signal_connect(glArea_, "key-press-event", G_CALLBACK(OnDrawingAreaKeyPress), this); + g_signal_connect(glArea_, "key-release-event", G_CALLBACK(OnDrawingAreaKeyRelease), this); + g_signal_connect(glArea_, "focus-in-event", G_CALLBACK(OnDrawingAreaFocusIn), this); + g_signal_connect(glArea_, "focus-out-event", G_CALLBACK(OnDrawingAreaFocusOut), this); + g_signal_connect(glArea_, "enter-notify-event", G_CALLBACK(OnDrawingAreaEnterNotify), this); + g_signal_connect(glArea_, "leave-notify-event", G_CALLBACK(OnDrawingAreaLeaveNotify), this); + g_signal_connect(glArea_, "map", G_CALLBACK(OnDrawingAreaMap), this); + g_signal_connect(glArea_, "unmap", G_CALLBACK(OnDrawingAreaUnmap), this); + g_signal_connect(glArea_, "size-allocate", G_CALLBACK(OnGlAreaSizeAllocate), this); + + gtk_box_pack_start(GTK_BOX(contentBox_), glArea_, TRUE, TRUE, 0); + useGlRendering_ = true; + } else { + // Fall back to original GtkDrawingArea implementation + setupDrawingAreaFallback(); + } +} + +void InAppBrowser::setupDrawingAreaFallback() { + // Original GtkDrawingArea code - used when GL rendering is not available + drawingArea_ = gtk_drawing_area_new(); + gtk_widget_set_can_focus(drawingArea_, TRUE); + gtk_widget_set_hexpand(drawingArea_, TRUE); + gtk_widget_set_vexpand(drawingArea_, TRUE); + + // Enable events + gtk_widget_add_events(drawingArea_, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); + + // Connect signals + g_signal_connect(drawingArea_, "draw", G_CALLBACK(OnDrawingAreaDraw), this); + g_signal_connect(drawingArea_, "realize", G_CALLBACK(OnDrawingAreaRealize), this); + g_signal_connect(drawingArea_, "button-press-event", G_CALLBACK(OnDrawingAreaButtonPress), this); + g_signal_connect(drawingArea_, "button-release-event", G_CALLBACK(OnDrawingAreaButtonRelease), + this); + g_signal_connect(drawingArea_, "motion-notify-event", G_CALLBACK(OnDrawingAreaMotionNotify), + this); + g_signal_connect(drawingArea_, "scroll-event", G_CALLBACK(OnDrawingAreaScroll), this); + g_signal_connect(drawingArea_, "key-press-event", G_CALLBACK(OnDrawingAreaKeyPress), this); + g_signal_connect(drawingArea_, "key-release-event", G_CALLBACK(OnDrawingAreaKeyRelease), this); + g_signal_connect(drawingArea_, "size-allocate", G_CALLBACK(OnDrawingAreaSizeAllocate), this); + g_signal_connect(drawingArea_, "focus-in-event", G_CALLBACK(OnDrawingAreaFocusIn), this); + g_signal_connect(drawingArea_, "focus-out-event", G_CALLBACK(OnDrawingAreaFocusOut), this); + g_signal_connect(drawingArea_, "map", G_CALLBACK(OnDrawingAreaMap), this); + g_signal_connect(drawingArea_, "unmap", G_CALLBACK(OnDrawingAreaUnmap), this); + g_signal_connect(drawingArea_, "enter-notify-event", G_CALLBACK(OnDrawingAreaEnterNotify), this); + g_signal_connect(drawingArea_, "leave-notify-event", G_CALLBACK(OnDrawingAreaLeaveNotify), this); + + gtk_box_pack_start(GTK_BOX(contentBox_), drawingArea_, TRUE, TRUE, 0); + useGlRendering_ = false; +} + +void InAppBrowser::setupWebView(const InAppBrowserCreationParams& params) { + if (messenger_ == nullptr || !FL_IS_BINARY_MESSENGER(messenger_)) { + errorLog("InAppBrowser::setupWebView - messenger is null or invalid"); + return; + } + + // Create InAppWebView creation params + InAppWebViewCreationParams webViewParams; + webViewParams.plugin = plugin_; + webViewParams.id = 0; // Will be set by channel attachment + webViewParams.gtkWindow = window_; + webViewParams.initialSettings = params.initialWebViewSettings; + webViewParams.contextMenu = params.contextMenu; + + if (params.initialUserScripts.has_value()) { + webViewParams.initialUserScripts = params.initialUserScripts.value(); + } + + // Check for WebViewEnvironment + if (params.webViewEnvironmentId.has_value() && !params.webViewEnvironmentId->empty()) { + WebViewEnvironment* webViewEnv = plugin_ ? plugin_->webViewEnvironment : nullptr; + WebKitWebContext* webContext = nullptr; + if (webViewEnv != nullptr) { + webContext = webViewEnv->getWebContext(params.webViewEnvironmentId.value()); + } + if (webContext != nullptr) { + webViewParams.webContext = webContext; + } + } + + // Create the InAppWebView - pass nullptr for registrar since we have messenger directly + webView_ = std::make_shared(nullptr, messenger_, 0, webViewParams); + + // Attach channel with browser-specific ID + webView_->AttachChannel(messenger_, METHOD_CHANNEL_NAME_PREFIX + id_, true); + + // Set initial size (will be updated on realize) + webView_->setSize(800, 600); + + // Set up frame callback - uses GL queue_render for GPU path, scheduleFrame for CPU path + webView_->SetOnFrameAvailable([this]() { + if (useGlRendering_ && glArea_ != nullptr) { + gtk_gl_area_queue_render(GTK_GL_AREA(glArea_)); + } else { + scheduleFrame(); + } + }); + + // Set up cursor change callback + webView_->SetOnCursorChanged([this](const std::string& cursorName) { + OnCursorChanged(cursorName); + }); + + // Set up progress change callback for progress bar + webView_->SetOnProgressChanged([this](double progress) { + didChangeProgress(progress); + }); + + // Set up navigation state change callback for back/forward buttons + webView_->SetOnNavigationStateChanged([this]() { + didChangeNavigationState(); + }); + + // Load initial content + loadInitialContent(params); +} + +void InAppBrowser::loadInitialContent(const InAppBrowserCreationParams& params) { + if (!webView_) { + return; + } + + if (params.urlRequest.has_value()) { + webView_->loadUrl(params.urlRequest.value()); + } else if (params.assetFilePath.has_value()) { + webView_->loadFile(params.assetFilePath.value()); + } else if (params.data.has_value()) { + webView_->loadData(params.data.value(), "text/html", "UTF-8", ""); + } +} + +void InAppBrowser::applySettings() { + if (!settings_) { + return; + } + + // Apply visibility + if (settings_->hideToolbarTop && headerBar_) { + gtk_widget_hide(headerBar_); + } + + // Apply window opacity + gtk_widget_set_opacity(GTK_WIDGET(window_), settings_->windowAlphaValue); +} + +void InAppBrowser::close() { + if (window_ != nullptr && !destroyed_) { + gtk_window_close(window_); + } +} + +void InAppBrowser::show() { + if (window_ != nullptr && !destroyed_) { + gtk_widget_show_all(GTK_WIDGET(window_)); + // Reapply visibility settings + if (settings_->hideToolbarTop && headerBar_) { + gtk_widget_hide(headerBar_); + } + if (settings_->hideProgressBar && progressBar_) { + gtk_widget_hide(progressBar_); + } + } +} + +void InAppBrowser::hide() { + if (window_ != nullptr && !destroyed_) { + gtk_widget_hide(GTK_WIDGET(window_)); + } +} + +bool InAppBrowser::isHidden() const { + if (window_ == nullptr || destroyed_) { + return true; + } + return !gtk_widget_is_visible(GTK_WIDGET(window_)); +} + +void InAppBrowser::setSettings(const std::shared_ptr& newSettings, + FlValue* newSettingsMap) { + if (!newSettings) { + return; + } + + // Update WebView settings + if (webView_ && newSettingsMap != nullptr) { + webView_->setSettings(std::make_shared(newSettingsMap), newSettingsMap); + } + + // Handle hidden change + if (fl_map_contains_not_null(newSettingsMap, "hidden") && + settings_->hidden != newSettings->hidden) { + if (newSettings->hidden) { + hide(); + } else { + show(); + } + } + + // Handle toolbar visibility change + if (fl_map_contains_not_null(newSettingsMap, "hideToolbarTop") && headerBar_ != nullptr) { + if (newSettings->hideToolbarTop) { + gtk_widget_hide(headerBar_); + } else { + gtk_widget_show(headerBar_); + } + } + + // Handle progress bar visibility + if (fl_map_contains_not_null(newSettingsMap, "hideProgressBar") && progressBar_ != nullptr) { + if (newSettings->hideProgressBar) { + gtk_widget_hide(progressBar_); + } else { + gtk_widget_show(progressBar_); + } + } + + // Handle title change + if (fl_map_contains_not_null(newSettingsMap, "toolbarTopFixedTitle") && + settings_->toolbarTopFixedTitle != newSettings->toolbarTopFixedTitle) { + if (!newSettings->toolbarTopFixedTitle.empty()) { + gtk_window_set_title(window_, newSettings->toolbarTopFixedTitle.c_str()); + } + } + + // Handle opacity change + if (fl_map_contains_not_null(newSettingsMap, "windowAlphaValue") && + settings_->windowAlphaValue != newSettings->windowAlphaValue) { + gtk_widget_set_opacity(GTK_WIDGET(window_), newSettings->windowAlphaValue); + } + + // Handle window frame change + if (fl_map_contains_not_null(newSettingsMap, "windowFrame") && newSettings->windowFrame) { + gtk_window_move(window_, static_cast(newSettings->windowFrame->x), + static_cast(newSettings->windowFrame->y)); + gtk_window_resize(window_, static_cast(newSettings->windowFrame->width), + static_cast(newSettings->windowFrame->height)); + } + + settings_ = newSettings; +} + +FlValue* InAppBrowser::getSettings() const { + if (!settings_) { + return fl_value_new_null(); + } + + FlValue* settingsMap = settings_->getRealSettings(this); + + // Merge with WebView settings + if (webView_) { + FlValue* webViewSettings = webView_->getSettings(); + if (webViewSettings != nullptr && fl_value_get_type(webViewSettings) == FL_VALUE_TYPE_MAP) { + size_t len = fl_value_get_length(webViewSettings); + for (size_t i = 0; i < len; i++) { + FlValue* key = fl_value_get_map_key(webViewSettings, i); + FlValue* value = fl_value_get_map_value(webViewSettings, i); + if (fl_value_get_type(key) == FL_VALUE_TYPE_STRING) { + fl_value_set_string_take(settingsMap, fl_value_get_string(key), fl_value_ref(value)); + } + } + } + } + + return settingsMap; +} + +void InAppBrowser::didChangeTitle(const std::optional& title) { + if (title.has_value() && settings_->toolbarTopFixedTitle.empty()) { + updateTitle(title.value()); + } +} + +void InAppBrowser::didChangeUrl(const std::optional& url) { + if (url.has_value()) { + updateUrlEntry(url.value()); + } + updateNavigationButtons(); +} + +void InAppBrowser::didChangeProgress(double progress) { + updateProgressBar(progress); +} + +void InAppBrowser::didChangeNavigationState() { + updateNavigationButtons(); +} + +void InAppBrowser::updateNavigationButtons() { + if (!webView_) { + return; + } + + if (backButton_ != nullptr) { + gtk_widget_set_sensitive(backButton_, webView_->canGoBack()); + } + if (forwardButton_ != nullptr) { + gtk_widget_set_sensitive(forwardButton_, webView_->canGoForward()); + } +} + +void InAppBrowser::updateProgressBar(double progress) { + if (progressBar_ == nullptr) { + return; + } + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressBar_), progress); + + // Hide progress bar when complete + if (progress >= 1.0) { + gtk_widget_hide(progressBar_); + } else if (!settings_->hideProgressBar) { + gtk_widget_show(progressBar_); + } +} + +void InAppBrowser::updateUrlEntry(const std::string& url) { + if (urlEntry_ != nullptr) { + gtk_entry_set_text(GTK_ENTRY(urlEntry_), url.c_str()); + } +} + +void InAppBrowser::updateTitle(const std::string& title) { + if (window_ != nullptr && !destroyed_) { + gtk_window_set_title(window_, title.c_str()); + } +} + +void InAppBrowser::scheduleFrame() { + if (destroyed_ || frameSourceId_ != 0) { + return; + } + frameSourceId_ = g_idle_add(OnFrameCallback, this); +} + +gboolean InAppBrowser::OnFrameCallback(gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && !browser->destroyed_) { + browser->frameSourceId_ = 0; + // This callback is only used for the CPU rendering path (GtkDrawingArea) + if (browser->drawingArea_ != nullptr) { + gtk_widget_queue_draw(browser->drawingArea_); + } + } else if (browser) { + browser->frameSourceId_ = 0; + } + return G_SOURCE_REMOVE; +} + +// Convert GDK modifier flags to WPE modifier flags +// GDK: Shift=1, Lock=2, Control=4, Mod1(Alt)=8, Mod4(Super)=64 +// WPE: Control=1, Shift=2, Alt=4, Meta=8 +static int ConvertGdkModifiersToWpe(guint gdkState) { + int wpeModifiers = 0; + if (gdkState & GDK_CONTROL_MASK) // GDK bit 2 -> WPE bit 0 + wpeModifiers |= 1; + if (gdkState & GDK_SHIFT_MASK) // GDK bit 0 -> WPE bit 1 + wpeModifiers |= 2; + if (gdkState & GDK_MOD1_MASK) // GDK bit 3 (Alt) -> WPE bit 2 + wpeModifiers |= 4; + if (gdkState & GDK_MOD4_MASK) // GDK bit 6 (Super/Meta) -> WPE bit 3 + wpeModifiers |= 8; + return wpeModifiers; +} + +// Convert RGBA to BGRA for Cairo ARGB32 format (which is BGRA on little-endian) +static void ConvertRGBAToBGRA(uint8_t* buffer, size_t size) { + // Process 4 bytes at a time (one pixel: RGBA -> BGRA) + for (size_t i = 0; i + 3 < size; i += 4) { + // Swap R and B channels + uint8_t r = buffer[i]; + buffer[i] = buffer[i + 2]; // B + buffer[i + 2] = r; // R + // G and A stay in place + } +} + +// GTK Signal handlers + +void InAppBrowser::OnWindowDestroy(GtkWidget* widget, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && !browser->destroyed_) { + // Notify Dart about exit + if (browser->channelDelegate_) { + browser->channelDelegate_->onExit(); + } + + // Remove from manager + if (browser->manager_) { + browser->manager_->removeBrowser(browser->id_); + } + } +} + +gboolean InAppBrowser::OnWindowDeleteEvent(GtkWidget* widget, GdkEvent* event, gpointer user_data) { + // Allow the window to be destroyed + return FALSE; +} + +void InAppBrowser::OnBackClicked(GtkButton* button, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->goBack(); + } +} + +void InAppBrowser::OnForwardClicked(GtkButton* button, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->goForward(); + } +} + +void InAppBrowser::OnReloadClicked(GtkButton* button, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->reload(); + } +} + +void InAppBrowser::OnUrlEntryActivated(GtkEntry* entry, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + const gchar* text = gtk_entry_get_text(entry); + if (text != nullptr && strlen(text) > 0) { + std::string url(text); + // Add http:// if no scheme specified + if (url.find("://") == std::string::npos) { + url = "https://" + url; + } + browser->webView_->loadUrl(url); + } + } +} + +gboolean InAppBrowser::OnDrawingAreaDraw(GtkWidget* widget, cairo_t* cr, gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || browser->destroyed_ || !browser->webView_) { + return FALSE; + } + + // Get the pixel buffer from WebView + uint32_t width = 0, height = 0; + size_t bufferSize = browser->webView_->GetPixelBufferSize(&width, &height); + + if (bufferSize == 0 || width == 0 || height == 0) { + // Fill with white background when no content + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_paint(cr); + return TRUE; + } + + // Allocate buffer and copy pixels + std::vector buffer(bufferSize); + if (!browser->webView_->CopyPixelBufferTo(buffer.data(), bufferSize, &width, &height)) { + return FALSE; + } + + // Convert RGBA to BGRA for Cairo + ConvertRGBAToBGRA(buffer.data(), bufferSize); + + // Create Cairo surface from pixel buffer (BGRA format) + cairo_surface_t* surface = cairo_image_surface_create_for_data( + buffer.data(), CAIRO_FORMAT_ARGB32, width, height, width * 4); + + if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) { + // Scale to fit drawing area + GtkAllocation alloc; + gtk_widget_get_allocation(widget, &alloc); + + double scaleX = static_cast(alloc.width) / width; + double scaleY = static_cast(alloc.height) / height; + + cairo_scale(cr, scaleX, scaleY); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + } + + cairo_surface_destroy(surface); + return TRUE; +} + +void InAppBrowser::OnDrawingAreaRealize(GtkWidget* widget, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + GtkAllocation alloc; + gtk_widget_get_allocation(widget, &alloc); + browser->webView_->setSize(alloc.width, alloc.height); + } +} + +gboolean InAppBrowser::OnDrawingAreaButtonPress(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + gtk_widget_grab_focus(widget); + + int button = 0; + switch (event->button) { + case 1: + button = 1; + break; // Primary + case 2: + button = 3; + break; // Tertiary (middle) + case 3: + button = 2; + break; // Secondary (right) + default: + button = static_cast(event->button); + } + + browser->webView_->SetCursorPos(event->x, event->y); + browser->webView_->SetPointerButton(1, button, 1); // Down + return TRUE; +} + +gboolean InAppBrowser::OnDrawingAreaButtonRelease(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + int button = 0; + switch (event->button) { + case 1: + button = 1; + break; + case 2: + button = 3; + break; + case 3: + button = 2; + break; + default: + button = static_cast(event->button); + } + + browser->webView_->SetCursorPos(event->x, event->y); + browser->webView_->SetPointerButton(4, button, 1); // Up + return TRUE; +} + +gboolean InAppBrowser::OnDrawingAreaMotionNotify(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + browser->webView_->SetCursorPos(event->x, event->y); + browser->webView_->SetPointerButton(5, 0, 0); // Update (motion) + return TRUE; +} + +gboolean InAppBrowser::OnDrawingAreaScroll(GtkWidget* widget, GdkEventScroll* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + double dx = 0, dy = 0; + + if (event->direction == GDK_SCROLL_SMOOTH) { + dx = event->delta_x * -53.0; + dy = event->delta_y * -53.0; + } else { + switch (event->direction) { + case GDK_SCROLL_UP: + dy = 53.0; + break; + case GDK_SCROLL_DOWN: + dy = -53.0; + break; + case GDK_SCROLL_LEFT: + dx = 53.0; + break; + case GDK_SCROLL_RIGHT: + dx = -53.0; + break; + default: + break; + } + } + + browser->webView_->SetScrollDelta(dx, dy); + return TRUE; +} + +gboolean InAppBrowser::OnDrawingAreaKeyPress(GtkWidget* widget, GdkEventKey* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + std::string characters; + if (event->string != nullptr && event->string[0] != '\0') { + characters = event->string; + } + + // Convert GDK modifiers to WPE format + int wpeModifiers = ConvertGdkModifiersToWpe(event->state); + + // type: 0=down, 1=up for WPE + browser->webView_->SendKeyEvent(0, event->keyval, event->hardware_keycode, wpeModifiers, + characters); + return TRUE; +} + +gboolean InAppBrowser::OnDrawingAreaKeyRelease(GtkWidget* widget, GdkEventKey* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (!browser || !browser->webView_) { + return FALSE; + } + + // Convert GDK modifiers to WPE format + int wpeModifiers = ConvertGdkModifiersToWpe(event->state); + + // type: 0=down, 1=up for WPE + browser->webView_->SendKeyEvent(1, event->keyval, event->hardware_keycode, wpeModifiers, ""); + return TRUE; +} + +void InAppBrowser::OnMenuItemActivated(GtkMenuItem* item, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->channelDelegate_) { + int32_t menuItemId = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "menu-item-id")); + browser->channelDelegate_->onMenuItemClicked(menuItemId); + } +} + +void InAppBrowser::OnDrawingAreaSizeAllocate(GtkWidget* widget, GdkRectangle* allocation, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_ && allocation) { + browser->webView_->setSize(allocation->width, allocation->height); + } +} + +gboolean InAppBrowser::OnDrawingAreaFocusIn(GtkWidget* widget, GdkEventFocus* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->setFocused(true); + } + return FALSE; +} + +gboolean InAppBrowser::OnDrawingAreaFocusOut(GtkWidget* widget, GdkEventFocus* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->setFocused(false); + } + return FALSE; +} + +void InAppBrowser::OnDrawingAreaMap(GtkWidget* widget, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->setVisible(true); + } +} + +void InAppBrowser::OnDrawingAreaUnmap(GtkWidget* widget, gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_) { + browser->webView_->setVisible(false); + } +} + +gboolean InAppBrowser::OnDrawingAreaEnterNotify(GtkWidget* widget, GdkEventCrossing* event, + gpointer user_data) { + // Mouse entered the drawing area - no special action needed + return FALSE; +} + +gboolean InAppBrowser::OnDrawingAreaLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser) { + // Reset cursor to default when leaving the drawing area + GdkWindow* gdkWindow = gtk_widget_get_window(widget); + if (gdkWindow) { + gdk_window_set_cursor(gdkWindow, nullptr); + } + if (browser->currentCursor_ != nullptr) { + g_object_unref(browser->currentCursor_); + browser->currentCursor_ = nullptr; + } + } + return FALSE; +} + +// === GtkGLArea Signal Handlers for GPU-Accelerated Rendering === + +void InAppBrowser::OnGlAreaRealize(GtkGLArea* area, gpointer user_data) { + auto* browser = static_cast(user_data); + + gtk_gl_area_make_current(area); + GError* error = gtk_gl_area_get_error(area); + if (error != nullptr) { + errorLog("InAppBrowser: GtkGLArea error during realize: %s", error->message); + return; + } + + // Create texture for EGL image binding + glGenTextures(1, &browser->glTexture_); + + // === Shader-based rendering setup (OpenGL ES compatible) === + // Vertex shader - transforms position and passes texture coords + const char* vertexSource = + "#version 100\n" + "attribute vec2 position;\n" + "attribute vec2 texture;\n" + "varying vec2 v_texture;\n" + "void main() {\n" + " v_texture = texture;\n" + " gl_Position = vec4(position, 0.0, 1.0);\n" + "}\n"; + + // Fragment shader - samples texture + const char* fragmentSource = + "#version 100\n" + "precision mediump float;\n" + "uniform sampler2D u_texture;\n" + "varying vec2 v_texture;\n" + "void main() {\n" + " gl_FragColor = texture2D(u_texture, v_texture);\n" + "}\n"; + + // Compile vertex shader + GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, &vertexSource, nullptr); + glCompileShader(vertexShader); + + GLint compiled = 0; + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLchar infoLog[512]; + glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog); + errorLog("InAppBrowser: Vertex shader compilation failed: %s", infoLog); + glDeleteShader(vertexShader); + return; + } + + // Compile fragment shader + GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &fragmentSource, nullptr); + glCompileShader(fragmentShader); + + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLchar infoLog[512]; + glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog); + errorLog("InAppBrowser: Fragment shader compilation failed: %s", infoLog); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + return; + } + + // Create and link program + browser->glProgram_ = glCreateProgram(); + glAttachShader(browser->glProgram_, vertexShader); + glAttachShader(browser->glProgram_, fragmentShader); + glLinkProgram(browser->glProgram_); + + GLint linked = 0; + glGetProgramiv(browser->glProgram_, GL_LINK_STATUS, &linked); + if (!linked) { + GLchar infoLog[512]; + glGetProgramInfoLog(browser->glProgram_, 512, nullptr, infoLog); + errorLog("InAppBrowser: Shader program linking failed: %s", infoLog); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + glDeleteProgram(browser->glProgram_); + browser->glProgram_ = 0; + return; + } + + // Shaders are linked into program, can delete them now + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + // Get attribute and uniform locations + browser->glAttribPosition_ = glGetAttribLocation(browser->glProgram_, "position"); + browser->glAttribTexture_ = glGetAttribLocation(browser->glProgram_, "texture"); + browser->glUniformTexture_ = glGetUniformLocation(browser->glProgram_, "u_texture"); + + // Create VBO with fullscreen quad vertex data (matching COG's layout) + // Non-interleaved: positions first, then texture coords + // Position (NDC): forms a quad covering -1 to 1 in both axes using TRIANGLE_STRIP + // Order: top-left, top-right, bottom-left, bottom-right + static const GLfloat vertexData[] = { + // Positions (8 floats) + -1.0f, 1.0f, // Top-left + 1.0f, 1.0f, // Top-right + -1.0f, -1.0f, // Bottom-left + 1.0f, -1.0f, // Bottom-right + // Texture coordinates (8 floats) - Y flipped for correct orientation + 0.0f, 0.0f, // Top-left + 1.0f, 0.0f, // Top-right + 0.0f, 1.0f, // Bottom-left + 1.0f, 1.0f, // Bottom-right + }; + + glGenBuffers(1, &browser->glVBO_); + glBindBuffer(GL_ARRAY_BUFFER, browser->glVBO_); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + browser->glInitialized_ = true; + + // Set initial size + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(area), &alloc); + if (browser->webView_) { + browser->webView_->setSize(alloc.width, alloc.height); + } +} + +gboolean InAppBrowser::OnGlAreaRender(GtkGLArea* area, GdkGLContext* context, + gpointer user_data) { + auto* browser = static_cast(user_data); + + if (!browser || browser->destroyed_ || !browser->webView_ || !browser->glInitialized_) { + return FALSE; + } + + // Clear the framebuffer with white background + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Get viewport dimensions - need to account for device scale factor + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(area), &alloc); + gint scaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(area)); + + // For high-DPI displays, the actual framebuffer is larger + int fbWidth = alloc.width * scaleFactor; + int fbHeight = alloc.height * scaleFactor; + + glViewport(0, 0, fbWidth, fbHeight); + + // Try to get EGL image for zero-copy rendering + uint32_t imgWidth = 0, imgHeight = 0; + void* eglImage = browser->webView_->GetCurrentEglImage(&imgWidth, &imgHeight); + + if (eglImage != nullptr && imgWidth > 0 && imgHeight > 0) { + // Get the glEGLImageTargetTexture2DOES function + static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = + (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + + if (glEGLImageTargetTexture2DOES != nullptr && browser->glProgram_ != 0) { + // Zero-copy path: bind EGL image directly as GL texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, browser->glTexture_); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast(eglImage)); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Use shader program for rendering + glUseProgram(browser->glProgram_); + glUniform1i(browser->glUniformTexture_, 0); + + // Bind VBO and set up vertex attributes (non-interleaved layout) + glBindBuffer(GL_ARRAY_BUFFER, browser->glVBO_); + + // Position attribute: 2 floats, stride 0 (tightly packed), offset 0 + glVertexAttribPointer(browser->glAttribPosition_, 2, GL_FLOAT, GL_FALSE, + 0, (void*)0); + glEnableVertexAttribArray(browser->glAttribPosition_); + + // Texture attribute: 2 floats, stride 0, offset = 4 positions * 2 floats = 8 floats + glVertexAttribPointer(browser->glAttribTexture_, 2, GL_FLOAT, GL_FALSE, + 0, (void*)(8 * sizeof(GLfloat))); + glEnableVertexAttribArray(browser->glAttribTexture_); + + // Draw fullscreen quad as triangle strip + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + + + // Cleanup state + glDisableVertexAttribArray(browser->glAttribPosition_); + glDisableVertexAttribArray(browser->glAttribTexture_); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); + + return TRUE; + } + } + + // Fallback: use pixel buffer rendering + return browser->RenderFromPixelBuffer(area); +} + +void InAppBrowser::OnGlAreaResize(GtkGLArea* area, gint width, gint height, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_ && width > 0 && height > 0) { + // GtkGLArea resize signal provides physical pixel dimensions + // WebView needs logical dimensions, so divide by scale factor + gint scaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(area)); + gint logicalWidth = width / scaleFactor; + gint logicalHeight = height / scaleFactor; + // Set scale factor for HiDPI rendering - WebView will render at physical resolution + browser->webView_->setScaleFactor(static_cast(scaleFactor)); + browser->webView_->setSize(logicalWidth, logicalHeight); + } +} + +void InAppBrowser::OnGlAreaSizeAllocate(GtkWidget* widget, GdkRectangle* allocation, + gpointer user_data) { + auto* browser = static_cast(user_data); + if (browser && browser->webView_ && allocation) { + if (allocation->width > 0 && allocation->height > 0) { + // size-allocate gives logical dimensions, which is what WebView needs + // Also set scale factor for HiDPI rendering + gint scaleFactor = gtk_widget_get_scale_factor(widget); + browser->webView_->setScaleFactor(static_cast(scaleFactor)); + browser->webView_->setSize(allocation->width, allocation->height); + // Queue a redraw to ensure the GL content is re-rendered at new size + if (browser->glArea_ != nullptr) { + gtk_gl_area_queue_render(GTK_GL_AREA(browser->glArea_)); + } + } + } +} + +gboolean InAppBrowser::RenderFromPixelBuffer(GtkGLArea* area) { + // Fallback: upload pixel buffer to texture and render + // This handles SHM mode where EGL images aren't available + + if (!webView_ || !glInitialized_ || glProgram_ == 0) { + return TRUE; // Nothing to render, but handled + } + + uint32_t width = 0, height = 0; + size_t bufferSize = webView_->GetPixelBufferSize(&width, &height); + + if (bufferSize == 0 || width == 0 || height == 0) { + return TRUE; // Nothing to render + } + + std::vector buffer(bufferSize); + if (!webView_->CopyPixelBufferTo(buffer.data(), bufferSize, &width, &height)) { + return FALSE; + } + + // Get viewport dimensions - account for device scale factor + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(area), &alloc); + gint scaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(area)); + int fbWidth = alloc.width * scaleFactor; + int fbHeight = alloc.height * scaleFactor; + + glViewport(0, 0, fbWidth, fbHeight); + + // Upload pixel buffer to texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, glTexture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, buffer.data()); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Use shader program for rendering + glUseProgram(glProgram_); + glUniform1i(glUniformTexture_, 0); + + // Bind VBO and set up vertex attributes (non-interleaved layout) + glBindBuffer(GL_ARRAY_BUFFER, glVBO_); + + // Position attribute: 2 floats, stride 0 (tightly packed), offset 0 + glVertexAttribPointer(glAttribPosition_, 2, GL_FLOAT, GL_FALSE, + 0, (void*)0); + glEnableVertexAttribArray(glAttribPosition_); + + // Texture attribute: 2 floats, stride 0, offset = 4 positions * 2 floats = 8 floats + glVertexAttribPointer(glAttribTexture_, 2, GL_FLOAT, GL_FALSE, + 0, (void*)(8 * sizeof(GLfloat))); + glEnableVertexAttribArray(glAttribTexture_); + + // Draw fullscreen quad as triangle strip + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Cleanup state + glDisableVertexAttribArray(glAttribPosition_); + glDisableVertexAttribArray(glAttribTexture_); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); + + return TRUE; +} + +void InAppBrowser::OnCursorChanged(const std::string& cursorName) { + // Handle cursor changes for both GL area and drawing area + GtkWidget* targetWidget = useGlRendering_ ? glArea_ : drawingArea_; + if (destroyed_ || targetWidget == nullptr) { + return; + } + + GdkWindow* gdkWindow = gtk_widget_get_window(targetWidget); + if (!gdkWindow) { + return; + } + + // Clean up previous cursor + if (currentCursor_ != nullptr) { + g_object_unref(currentCursor_); + currentCursor_ = nullptr; + } + + if (cursorName == "none") { + currentCursor_ = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR); + gdk_window_set_cursor(gdkWindow, currentCursor_); + return; + } + + // Use gdk_cursor_new_from_name which accepts CSS cursor names (GTK 3.16+) + GdkDisplay* display = gdk_display_get_default(); + currentCursor_ = gdk_cursor_new_from_name(display, cursorName.c_str()); + + // Fallback if cursor name not recognized + if (!currentCursor_) { + currentCursor_ = gdk_cursor_new_from_name(display, "default"); + } + + // Ultimate fallback to GDK_LEFT_PTR + if (!currentCursor_) { + currentCursor_ = gdk_cursor_new_for_display(display, GDK_LEFT_PTR); + } + + gdk_window_set_cursor(gdkWindow, currentCursor_); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.h b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.h new file mode 100644 index 00000000..2d469e6a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser.h @@ -0,0 +1,235 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ + +#include +#include + +#include +#include +#include +#include + +#include "../in_app_webview/in_app_webview.h" +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "in_app_browser_channel_delegate.h" +#include "in_app_browser_settings.h" + +namespace flutter_inappwebview_plugin { + +class InAppBrowserManager; +class PluginInstance; + +/// Menu item for InAppBrowser +struct InAppBrowserMenuItem { + int32_t id = 0; + std::string title; + int32_t order = 0; + + InAppBrowserMenuItem() = default; + explicit InAppBrowserMenuItem(FlValue* map); +}; + +/// Creation parameters for InAppBrowser +struct InAppBrowserCreationParams { + PluginInstance* plugin = nullptr; + std::string id; + std::optional> urlRequest; + std::optional assetFilePath; + std::optional data; + std::shared_ptr initialSettings; + std::shared_ptr initialWebViewSettings; + std::optional>> initialUserScripts; + std::optional webViewEnvironmentId; + std::optional> contextMenu; + std::vector menuItems; +}; + +/// InAppBrowser - A standalone browser window with embedded WebView +/// +/// Creates a GTK window with an optional toolbar and an embedded WPE WebView. +/// Supports navigation controls, URL bar, progress indicator, and custom menus. +/// +/// The browser window can be shown, hidden, and configured with various settings +/// like window size, position, opacity, and toolbar visibility. +class InAppBrowser { + public: + static constexpr const char* METHOD_CHANNEL_NAME_PREFIX = + "com.pichillilorenzo/flutter_inappbrowser_"; + + /// Create an InAppBrowser with the given parameters + /// @param manager The manager that owns this browser + /// @param messenger The Flutter binary messenger for channel communication + /// @param parentWindow The parent GTK window (optional, for child window type) + /// @param params Creation parameters + InAppBrowser(InAppBrowserManager* manager, FlBinaryMessenger* messenger, + GtkWindow* parentWindow, const InAppBrowserCreationParams& params); + ~InAppBrowser(); + + /// Get the unique ID of this browser + const std::string& id() const { return id_; } + + /// Get the GTK window + GtkWindow* getWindow() const { return window_; } + + /// Get the embedded WebView + InAppWebView* webView() const { return webView_.get(); } + + /// Get the channel delegate + InAppBrowserChannelDelegate* channelDelegate() const { return channelDelegate_.get(); } + + /// Get the current settings + std::shared_ptr settings() const { return settings_; } + + /// Close the browser window + void close(); + + /// Show the browser window + void show(); + + /// Hide the browser window + void hide(); + + /// Check if the browser is hidden + bool isHidden() const; + + /// Update the browser settings + /// @param newSettings The new settings to apply + /// @param newSettingsMap The FlValue map of new settings (for checking which changed) + void setSettings(const std::shared_ptr& newSettings, + FlValue* newSettingsMap); + + /// Get the current settings as FlValue + FlValue* getSettings() const; + + /// Called when the WebView title changes + void didChangeTitle(const std::optional& title); + + /// Called when the WebView URL changes + void didChangeUrl(const std::optional& url); + + /// Called when the load progress changes + void didChangeProgress(double progress); + + /// Called when navigation state changes (can go back/forward) + void didChangeNavigationState(); + + private: + PluginInstance* plugin_ = nullptr; + InAppBrowserManager* manager_ = nullptr; + FlBinaryMessenger* messenger_ = nullptr; + GtkWindow* parentWindow_ = nullptr; // Parent window for child window type + std::string id_; + bool destroyed_ = false; + + // GTK Window (created by this browser) + GtkWindow* window_ = nullptr; + + // Toolbar widgets + GtkWidget* headerBar_ = nullptr; + GtkWidget* backButton_ = nullptr; + GtkWidget* forwardButton_ = nullptr; + GtkWidget* reloadButton_ = nullptr; + GtkWidget* urlEntry_ = nullptr; + GtkWidget* progressBar_ = nullptr; + GtkWidget* menuButton_ = nullptr; + GtkWidget* contentBox_ = nullptr; + + // WebView rendering + std::shared_ptr webView_; + GtkWidget* drawingArea_ = nullptr; + guint frameSourceId_ = 0; + GdkCursor* currentCursor_ = nullptr; + + // GPU rendering with GtkGLArea (zero-copy EGL texture path) + GtkWidget* glArea_ = nullptr; + bool useGlRendering_ = false; + unsigned int glTexture_ = 0; + bool glInitialized_ = false; + + // Shader-based rendering (OpenGL ES compatible) + unsigned int glProgram_ = 0; + unsigned int glVBO_ = 0; + int glAttribPosition_ = -1; + int glAttribTexture_ = -1; + int glUniformTexture_ = -1; + + // Channel delegate + std::unique_ptr channelDelegate_; + + // Settings + std::shared_ptr settings_; + + // Menu items + std::vector menuItems_; + + // Window setup methods + void setupWindow(const InAppBrowserCreationParams& params); + void setupToolbar(); + void setupWebView(const InAppBrowserCreationParams& params); + void setupDrawingArea(); + void applySettings(); + void loadInitialContent(const InAppBrowserCreationParams& params); + + // Update toolbar state + void updateNavigationButtons(); + void updateProgressBar(double progress); + void updateUrlEntry(const std::string& url); + void updateTitle(const std::string& title); + + // GTK Signal handlers (static callbacks) + static void OnWindowDestroy(GtkWidget* widget, gpointer user_data); + static gboolean OnWindowDeleteEvent(GtkWidget* widget, GdkEvent* event, gpointer user_data); + static void OnBackClicked(GtkButton* button, gpointer user_data); + static void OnForwardClicked(GtkButton* button, gpointer user_data); + static void OnReloadClicked(GtkButton* button, gpointer user_data); + static void OnUrlEntryActivated(GtkEntry* entry, gpointer user_data); + static gboolean OnDrawingAreaDraw(GtkWidget* widget, cairo_t* cr, gpointer user_data); + static void OnDrawingAreaRealize(GtkWidget* widget, gpointer user_data); + static gboolean OnDrawingAreaButtonPress(GtkWidget* widget, GdkEventButton* event, + gpointer user_data); + static gboolean OnDrawingAreaButtonRelease(GtkWidget* widget, GdkEventButton* event, + gpointer user_data); + static gboolean OnDrawingAreaMotionNotify(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data); + static gboolean OnDrawingAreaScroll(GtkWidget* widget, GdkEventScroll* event, + gpointer user_data); + static gboolean OnDrawingAreaKeyPress(GtkWidget* widget, GdkEventKey* event, + gpointer user_data); + static gboolean OnDrawingAreaKeyRelease(GtkWidget* widget, GdkEventKey* event, + gpointer user_data); + static void OnDrawingAreaSizeAllocate(GtkWidget* widget, GdkRectangle* allocation, gpointer user_data); + static gboolean OnDrawingAreaFocusIn(GtkWidget* widget, GdkEventFocus* event, gpointer user_data); + static gboolean OnDrawingAreaFocusOut(GtkWidget* widget, GdkEventFocus* event, gpointer user_data); + static void OnDrawingAreaMap(GtkWidget* widget, gpointer user_data); + static void OnDrawingAreaUnmap(GtkWidget* widget, gpointer user_data); + static gboolean OnDrawingAreaEnterNotify(GtkWidget* widget, GdkEventCrossing* event, gpointer user_data); + static gboolean OnDrawingAreaLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, gpointer user_data); + static void OnMenuItemActivated(GtkMenuItem* item, gpointer user_data); + + // GtkGLArea signal handlers (for GPU-accelerated rendering) + static void OnGlAreaRealize(GtkGLArea* area, gpointer user_data); + static gboolean OnGlAreaRender(GtkGLArea* area, GdkGLContext* context, gpointer user_data); + static void OnGlAreaResize(GtkGLArea* area, gint width, gint height, gpointer user_data); + static void OnGlAreaSizeAllocate(GtkWidget* widget, GdkRectangle* allocation, gpointer user_data); + + // Fallback pixel buffer rendering (for SHM mode or when EGL is unavailable) + gboolean RenderFromPixelBuffer(GtkGLArea* area); + + // Fallback setup for GtkDrawingArea (when GtkGLArea fails) + void setupDrawingAreaFallback(); + + // Cursor change handler + void OnCursorChanged(const std::string& cursorName); + + // Schedule frame redraw + void scheduleFrame(); + static gboolean OnFrameCallback(gpointer user_data); + + // Clean up + void cleanup(); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.cc new file mode 100644 index 00000000..abdb088e --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.cc @@ -0,0 +1,98 @@ +#include "in_app_browser_channel_delegate.h" + +#include + +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin { + +InAppBrowserChannelDelegate::InAppBrowserChannelDelegate(InAppBrowser* browser, + FlBinaryMessenger* messenger, + const std::string& channelName) + : ChannelDelegate(messenger, channelName), browser_(browser) {} + +InAppBrowserChannelDelegate::~InAppBrowserChannelDelegate() { + debugLog("dealloc InAppBrowserChannelDelegate"); + browser_ = nullptr; +} + +void InAppBrowserChannelDelegate::HandleMethodCall(FlMethodCall* method_call) { + const gchar* methodName = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (browser_ == nullptr) { + fl_method_call_respond_error(method_call, "ERROR", "Browser instance is null", nullptr, + nullptr); + return; + } + + if (strcmp(methodName, "show") == 0) { + browser_->show(); + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); + return; + } + + if (strcmp(methodName, "hide") == 0) { + browser_->hide(); + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); + return; + } + + if (strcmp(methodName, "close") == 0) { + browser_->close(); + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); + return; + } + + if (strcmp(methodName, "isHidden") == 0) { + bool hidden = browser_->isHidden(); + fl_method_call_respond_success(method_call, fl_value_new_bool(hidden), nullptr); + return; + } + + if (strcmp(methodName, "setSettings") == 0) { + FlValue* settingsValue = fl_value_lookup_string(args, "settings"); + if (settingsValue != nullptr && fl_value_get_type(settingsValue) == FL_VALUE_TYPE_MAP) { + auto newSettings = std::make_shared(settingsValue); + browser_->setSettings(newSettings, settingsValue); + } + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); + return; + } + + if (strcmp(methodName, "getSettings") == 0) { + g_autoptr(FlValue) result = browser_->getSettings(); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void InAppBrowserChannelDelegate::onBrowserCreated() const { + if (channel_ == nullptr) { + return; + } + invokeMethod("onBrowserCreated", nullptr); +} + +void InAppBrowserChannelDelegate::onMenuItemClicked(int32_t menuItemId) const { + if (channel_ == nullptr) { + return; + } + g_autoptr(FlValue) args = to_fl_map({ + {"id", make_fl_value(static_cast(menuItemId))}, + }); + invokeMethod("onMenuItemClicked", args); +} + +void InAppBrowserChannelDelegate::onExit() const { + if (channel_ == nullptr) { + return; + } + invokeMethod("onExit", nullptr); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.h b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.h new file mode 100644 index 00000000..3d539683 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_channel_delegate.h @@ -0,0 +1,47 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ + +#include + +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class InAppBrowser; + +/// Channel delegate for InAppBrowser instance communication +/// +/// Handles method calls from Dart and sends events back to the Dart side. +/// Each InAppBrowser instance has its own channel delegate with a unique channel name. +class InAppBrowserChannelDelegate : public ChannelDelegate { + public: + /// Create a channel delegate for a specific browser instance + /// @param browser The browser instance this delegate belongs to + /// @param messenger The Flutter binary messenger + /// @param channelName The unique channel name for this browser + InAppBrowserChannelDelegate(InAppBrowser* browser, FlBinaryMessenger* messenger, + const std::string& channelName); + ~InAppBrowserChannelDelegate() override; + + /// Handle method calls from Flutter + void HandleMethodCall(FlMethodCall* method_call) override; + + /// Notify Dart that the browser window has been created + void onBrowserCreated() const; + + /// Notify Dart that a menu item was clicked + /// @param menuItemId The ID of the clicked menu item + void onMenuItemClicked(int32_t menuItemId) const; + + /// Notify Dart that the browser window is about to close + void onExit() const; + + private: + InAppBrowser* browser_ = nullptr; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.cc new file mode 100644 index 00000000..e7891f30 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.cc @@ -0,0 +1,234 @@ +#include "in_app_browser_manager.h" + +#include + +#include + +#include "../plugin_instance.h" +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "../types/user_script.h" +#include "../types/context_menu.h" +#include "../utils/flutter.h" +#include "../utils/log.h" + +namespace flutter_inappwebview_plugin { + +InAppBrowserManager::InAppBrowserManager(PluginInstance* plugin) : plugin_(plugin) { + // Validate plugin + if (plugin_ == nullptr) { + errorLog("InAppBrowserManager: Invalid plugin instance"); + return; + } + + registrar_ = plugin_->registrar(); + if (!FL_IS_PLUGIN_REGISTRAR(registrar_)) { + errorLog("InAppBrowserManager: Invalid registrar"); + return; + } + + messenger_ = plugin_->messenger(); + if (messenger_ == nullptr || !FL_IS_BINARY_MESSENGER(messenger_)) { + errorLog("InAppBrowserManager: Failed to get messenger from plugin"); + messenger_ = nullptr; + return; + } + + // Keep reference to messenger to ensure it stays valid + g_object_ref(messenger_); + + // Cache the GTK window and FlView from plugin + gtk_window_ = plugin_->gtkWindow(); + fl_view_ = plugin_->flView(); + + // Create the method channel + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + method_channel_ = fl_method_channel_new(messenger_, METHOD_CHANNEL_NAME, FL_METHOD_CODEC(codec)); + + fl_method_channel_set_method_call_handler(method_channel_, HandleMethodCall, this, nullptr); +} + +InAppBrowserManager::~InAppBrowserManager() { + debugLog("dealloc InAppBrowserManager"); + + // Clean up all browsers first + browsers_.clear(); + + if (method_channel_ != nullptr) { + fl_method_channel_set_method_call_handler(method_channel_, nullptr, nullptr, nullptr); + g_object_unref(method_channel_); + method_channel_ = nullptr; + } + + // Release messenger reference + if (messenger_ != nullptr) { + g_object_unref(messenger_); + messenger_ = nullptr; + } + + gtk_window_ = nullptr; + fl_view_ = nullptr; + registrar_ = nullptr; + plugin_ = nullptr; +} + +FlPluginRegistrar* InAppBrowserManager::registrar() const { + return registrar_; +} + +void InAppBrowserManager::HandleMethodCall(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + auto* self = static_cast(user_data); + self->HandleMethodCallImpl(method_call); +} + +void InAppBrowserManager::HandleMethodCallImpl(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (strcmp(method, "open") == 0) { + createInAppBrowser(args); + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); + return; + } + + if (strcmp(method, "openWithSystemBrowser") == 0) { + auto urlOpt = get_optional_fl_map_value(args, "url"); + if (urlOpt.has_value()) { + openWithSystemBrowser(urlOpt.value(), method_call); + } else { + fl_method_call_respond_error(method_call, "INVALID_ARGUMENTS", "URL is required", nullptr, + nullptr); + } + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void InAppBrowserManager::createInAppBrowser(FlValue* arguments) { + if (arguments == nullptr || fl_value_get_type(arguments) != FL_VALUE_TYPE_MAP) { + return; + } + + // Parse arguments + std::string id = get_fl_map_value(arguments, "id", ""); + if (id.empty()) { + debugLog("InAppBrowserManager: Missing browser ID"); + return; + } + + // Parse URL request + std::optional> urlRequest; + FlValue* urlRequestValue = fl_value_lookup_string(arguments, "urlRequest"); + if (urlRequestValue != nullptr && fl_value_get_type(urlRequestValue) == FL_VALUE_TYPE_MAP) { + urlRequest = std::make_shared(urlRequestValue); + } + + // Parse asset file path + auto assetFilePath = get_optional_fl_map_value(arguments, "assetFilePath"); + + // Parse data + auto data = get_optional_fl_map_value(arguments, "data"); + + // Parse settings + FlValue* settingsValue = fl_value_lookup_string(arguments, "settings"); + auto initialSettings = std::make_shared(settingsValue); + auto initialWebViewSettings = std::make_shared(settingsValue); + + // Parse user scripts + std::optional>> initialUserScripts; + FlValue* userScriptsValue = fl_value_lookup_string(arguments, "initialUserScripts"); + if (userScriptsValue != nullptr && fl_value_get_type(userScriptsValue) == FL_VALUE_TYPE_LIST) { + std::vector> scripts; + size_t count = fl_value_get_length(userScriptsValue); + for (size_t i = 0; i < count; i++) { + FlValue* scriptValue = fl_value_get_list_value(userScriptsValue, i); + if (scriptValue != nullptr && fl_value_get_type(scriptValue) == FL_VALUE_TYPE_MAP) { + scripts.push_back(std::make_shared(scriptValue)); + } + } + if (!scripts.empty()) { + initialUserScripts = scripts; + } + } + + // Parse WebView environment ID + auto webViewEnvironmentId = get_optional_fl_map_value(arguments, "webViewEnvironmentId"); + + // Parse context menu + std::optional> contextMenu; + FlValue* contextMenuValue = fl_value_lookup_string(arguments, "contextMenu"); + if (contextMenuValue != nullptr && fl_value_get_type(contextMenuValue) == FL_VALUE_TYPE_MAP) { + contextMenu = std::make_shared(contextMenuValue); + } + + // Parse menu items + std::vector menuItems; + FlValue* menuItemsValue = fl_value_lookup_string(arguments, "menuItems"); + if (menuItemsValue != nullptr && fl_value_get_type(menuItemsValue) == FL_VALUE_TYPE_LIST) { + size_t count = fl_value_get_length(menuItemsValue); + for (size_t i = 0; i < count; i++) { + FlValue* itemValue = fl_value_get_list_value(menuItemsValue, i); + if (itemValue != nullptr && fl_value_get_type(itemValue) == FL_VALUE_TYPE_MAP) { + menuItems.push_back(InAppBrowserMenuItem(itemValue)); + } + } + } + + // Create params + InAppBrowserCreationParams params; + params.plugin = plugin_; + params.id = id; + params.urlRequest = urlRequest; + params.assetFilePath = assetFilePath; + params.data = data; + params.initialSettings = initialSettings; + params.initialWebViewSettings = initialWebViewSettings; + params.initialUserScripts = initialUserScripts; + params.webViewEnvironmentId = webViewEnvironmentId; + params.contextMenu = contextMenu; + params.menuItems = menuItems; + + // Create the browser - pass cached messenger directly + auto browser = std::make_unique(this, messenger_, gtk_window_, params); + browsers_[id] = std::move(browser); +} + +void InAppBrowserManager::openWithSystemBrowser(const std::string& url, + FlMethodCall* method_call) { + GError* error = nullptr; + + // Use GIO to open the URL with the default handler + gboolean success = g_app_info_launch_default_for_uri(url.c_str(), nullptr, &error); + + if (!success) { + std::string errorMsg = "Failed to open URL"; + if (error != nullptr) { + errorMsg = error->message; + g_error_free(error); + } + debugLog("InAppBrowserManager: " + errorMsg + " - " + url); + fl_method_call_respond_error(method_call, "OPEN_URL_ERROR", errorMsg.c_str(), nullptr, nullptr); + return; + } + + fl_method_call_respond_success(method_call, fl_value_new_bool(true), nullptr); +} + +void InAppBrowserManager::removeBrowser(const std::string& id) { + auto it = browsers_.find(id); + if (it != browsers_.end()) { + browsers_.erase(it); + } +} + +InAppBrowser* InAppBrowserManager::getBrowser(const std::string& id) { + auto it = browsers_.find(id); + if (it != browsers_.end()) { + return it->second.get(); + } + return nullptr; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.h b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.h new file mode 100644 index 00000000..a886b7fc --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_manager.h @@ -0,0 +1,76 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ + +#include + +#include +#include +#include + +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/// Manager class for InAppBrowser instances +/// +/// Handles the static method channel for creating browsers and opening URLs +/// with the system browser. Maintains a map of all active browser instances. +class InAppBrowserManager { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_inappbrowser"; + + /// Create the manager + /// @param plugin The plugin instance + InAppBrowserManager(PluginInstance* plugin); + ~InAppBrowserManager(); + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + /// Get the registrar + FlPluginRegistrar* registrar() const; + + /// Get the messenger + FlBinaryMessenger* messenger() const { return messenger_; } + + /// Create a new InAppBrowser instance + /// @param arguments The creation arguments from Dart + void createInAppBrowser(FlValue* arguments); + + /// Open a URL with the system browser + /// @param url The URL to open + /// @param result The method result to respond to + void openWithSystemBrowser(const std::string& url, FlMethodCall* method_call); + + /// Remove a browser from the manager (called when browser is destroyed) + /// @param id The browser ID to remove + void removeBrowser(const std::string& id); + + /// Get a browser by ID + /// @param id The browser ID + /// @return The browser instance or nullptr + InAppBrowser* getBrowser(const std::string& id); + + private: + PluginInstance* plugin_ = nullptr; + FlPluginRegistrar* registrar_ = nullptr; + FlBinaryMessenger* messenger_ = nullptr; + FlMethodChannel* method_channel_ = nullptr; + GtkWindow* gtk_window_ = nullptr; + FlView* fl_view_ = nullptr; + + /// Map of browser ID to browser instance + std::map> browsers_; + + /// Handle method calls from Flutter + static void HandleMethodCall(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data); + void HandleMethodCallImpl(FlMethodCall* method_call); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.cc new file mode 100644 index 00000000..cfd48354 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.cc @@ -0,0 +1,127 @@ +#include "in_app_browser_settings.h" + +#include + +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin { + +namespace { + +InAppBrowserWindowType windowTypeFromString(const std::string& s) { + if (s == "CHILD") { + return InAppBrowserWindowType::child; + } + return InAppBrowserWindowType::window; +} + +std::string windowTypeToString(InAppBrowserWindowType t) { + switch (t) { + case InAppBrowserWindowType::child: + return "CHILD"; + default: + return "WINDOW"; + } +} + +} // namespace + +// InAppBrowserRect implementation + +InAppBrowserRect::InAppBrowserRect(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + x = get_fl_map_value(map, "x", 0); + y = get_fl_map_value(map, "y", 0); + width = get_fl_map_value(map, "width", 0); + height = get_fl_map_value(map, "height", 0); +} + +FlValue* InAppBrowserRect::toFlValue() const { + return to_fl_map({ + {"x", make_fl_value(x)}, + {"y", make_fl_value(y)}, + {"width", make_fl_value(width)}, + {"height", make_fl_value(height)}, + }); +} + +// InAppBrowserSettings implementation + +InAppBrowserSettings::InAppBrowserSettings() = default; + +InAppBrowserSettings::InAppBrowserSettings(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + hidden = get_fl_map_value(map, "hidden", hidden); + hideToolbarTop = get_fl_map_value(map, "hideToolbarTop", hideToolbarTop); + toolbarTopBackgroundColor = get_fl_map_value(map, "toolbarTopBackgroundColor", toolbarTopBackgroundColor); + hideUrlBar = get_fl_map_value(map, "hideUrlBar", hideUrlBar); + hideProgressBar = get_fl_map_value(map, "hideProgressBar", hideProgressBar); + hideDefaultMenuItems = get_fl_map_value(map, "hideDefaultMenuItems", hideDefaultMenuItems); + toolbarTopFixedTitle = get_fl_map_value(map, "toolbarTopFixedTitle", toolbarTopFixedTitle); + + auto windowTypeStr = get_fl_map_value(map, "windowType", "WINDOW"); + windowType = windowTypeFromString(windowTypeStr); + + windowAlphaValue = get_fl_map_value(map, "windowAlphaValue", windowAlphaValue); + + FlValue* frameValue = fl_value_lookup_string(map, "windowFrame"); + if (frameValue != nullptr && fl_value_get_type(frameValue) == FL_VALUE_TYPE_MAP) { + windowFrame = std::make_shared(frameValue); + } +} + +InAppBrowserSettings::~InAppBrowserSettings() { + debugLog("dealloc InAppBrowserSettings"); +} + +FlValue* InAppBrowserSettings::toFlValue() const { + FlValue* result = to_fl_map({ + {"hidden", make_fl_value(hidden)}, + {"hideToolbarTop", make_fl_value(hideToolbarTop)}, + {"toolbarTopBackgroundColor", make_fl_value(toolbarTopBackgroundColor)}, + {"hideUrlBar", make_fl_value(hideUrlBar)}, + {"hideProgressBar", make_fl_value(hideProgressBar)}, + {"hideDefaultMenuItems", make_fl_value(hideDefaultMenuItems)}, + {"toolbarTopFixedTitle", make_fl_value(toolbarTopFixedTitle)}, + {"windowType", make_fl_value(windowTypeToString(windowType))}, + {"windowAlphaValue", make_fl_value(windowAlphaValue)}, + {"windowFrame", windowFrame ? windowFrame->toFlValue() : fl_value_new_null()}, + }); + return result; +} + +FlValue* InAppBrowserSettings::getRealSettings(const InAppBrowser* browser) const { + FlValue* settingsMap = toFlValue(); + + if (browser != nullptr) { + GtkWindow* window = browser->getWindow(); + if (window != nullptr) { + // Update hidden state + fl_value_set_string_take(settingsMap, "hidden", + make_fl_value(!gtk_widget_is_visible(GTK_WIDGET(window)))); + + // Update window opacity + fl_value_set_string_take(settingsMap, "windowAlphaValue", + make_fl_value(gtk_widget_get_opacity(GTK_WIDGET(window)))); + + // Update window frame + int x = 0, y = 0, width = 0, height = 0; + gtk_window_get_position(window, &x, &y); + gtk_window_get_size(window, &width, &height); + InAppBrowserRect frame(static_cast(x), static_cast(y), + static_cast(width), static_cast(height)); + fl_value_set_string_take(settingsMap, "windowFrame", frame.toFlValue()); + } + } + + return settingsMap; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.h b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.h new file mode 100644 index 00000000..71bbe77f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_browser/in_app_browser_settings.h @@ -0,0 +1,83 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class InAppBrowser; + +/// Window type for InAppBrowser +enum class InAppBrowserWindowType { + window, // Top-level window + child // Child window (transient for parent) +}; + +/// Rect structure for window frame +struct InAppBrowserRect { + double x = 0; + double y = 0; + double width = 0; + double height = 0; + + InAppBrowserRect() = default; + InAppBrowserRect(double x, double y, double width, double height) + : x(x), y(y), width(width), height(height) {} + explicit InAppBrowserRect(FlValue* map); + + FlValue* toFlValue() const; +}; + +/// Settings class for InAppBrowser +/// +/// Stores configuration options for the browser window appearance and behavior. +class InAppBrowserSettings { + public: + /// Start with hidden window + bool hidden = false; + + /// Hide the top toolbar + bool hideToolbarTop = false; + + /// Toolbar background color (CSS color string, e.g., "#RRGGBB" or "#AARRGGBB") + std::string toolbarTopBackgroundColor; + + /// Hide the URL bar in the toolbar + bool hideUrlBar = false; + + /// Hide the progress bar + bool hideProgressBar = false; + + /// Hide the default menu items (back, forward, reload) + bool hideDefaultMenuItems = false; + + /// Fixed title for the window (instead of using page title) + std::string toolbarTopFixedTitle; + + /// Window type (top-level or child) + InAppBrowserWindowType windowType = InAppBrowserWindowType::window; + + /// Window opacity (0.0 to 1.0) + double windowAlphaValue = 1.0; + + /// Window frame (position and size) + std::shared_ptr windowFrame; + + InAppBrowserSettings(); + explicit InAppBrowserSettings(FlValue* map); + ~InAppBrowserSettings(); + + /// Convert settings to FlValue map + FlValue* toFlValue() const; + + /// Get actual settings from a live browser instance + FlValue* getRealSettings(const InAppBrowser* browser) const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.cc new file mode 100644 index 00000000..6ed81d84 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.cc @@ -0,0 +1,629 @@ +#include "custom_platform_view.h" + +#include + +#include + +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "inappwebview_egl_texture.h" +#include "inappwebview_texture.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Check if GL textures should be used (enabled by default, can be disabled) +// Disable with FLUTTER_INAPPWEBVIEW_LINUX_DISABLE_GL=1 to force software rendering. +bool UseGLTextureEnvOverride() { + if (g_getenv("FLUTTER_INAPPWEBVIEW_LINUX_DISABLE_GL") != nullptr) { + return false; + } + return true; +} + +// Check if OpenGL is actually available in the current GDK backend +bool IsOpenGLAvailable() { + static bool checked = false; + static bool available = false; + + if (checked) { + return available; + } + checked = true; + + GdkDisplay* display = gdk_display_get_default(); + if (display == nullptr) { + debugLog("CustomPlatformView: No GDK display available"); + return false; + } + + // Try to create a temporary window to test GL support + GdkWindowAttr attrs; + memset(&attrs, 0, sizeof(attrs)); + attrs.width = 1; + attrs.height = 1; + attrs.wclass = GDK_INPUT_OUTPUT; + attrs.window_type = GDK_WINDOW_TOPLEVEL; + + GdkWindow* test_window = gdk_window_new(nullptr, &attrs, 0); + if (test_window == nullptr) { + debugLog("CustomPlatformView: Failed to create test window for GL check"); + return false; + } + + GError* error = nullptr; + GdkGLContext* gl_context = gdk_window_create_gl_context(test_window, &error); + + if (gl_context != nullptr) { + available = true; + g_object_unref(gl_context); + debugLog("CustomPlatformView: OpenGL is available"); + } else { + debugLog(std::string("CustomPlatformView: OpenGL not available: ") + + (error ? error->message : "unknown error")); + if (error) { + g_error_free(error); + } + } + + gdk_window_destroy(test_window); + + return available; +} + +bool UseGLTexture() { + if (!UseGLTextureEnvOverride()) { + return false; + } + return IsOpenGLAvailable(); +} +} // namespace + +CustomPlatformView::CustomPlatformView(FlBinaryMessenger* messenger, + FlTextureRegistrar* texture_registrar, + std::shared_ptr webview) + : webview_(std::move(webview)), texture_registrar_(texture_registrar) { + if (messenger == nullptr) { + errorLog("CustomPlatformView: messenger is null"); + return; + } + + if (texture_registrar_ == nullptr) { + errorLog("CustomPlatformView: texture_registrar is null"); + return; + } + + // Create texture - two modes: + // 1. EGL/GL texture (hardware accelerated) - uses zero-copy EGL when available, + // falls back to pixel buffer upload when EGL is not available (e.g., in VMs) + // 2. Pixel buffer texture (software) - pure software fallback when GL is disabled + // + // The EGL texture handles both EGL and SHM modes internally, providing the best + // performance for each environment. + if (UseGLTexture()) { + texture_ = FL_TEXTURE(inappwebview_egl_texture_new(webview_.get())); + egl_texture_ = INAPPWEBVIEW_EGL_TEXTURE(texture_); + // In zero-copy EGL mode, we don't need pixel readback - the EGL image is passed + // directly to Flutter. This improves performance and avoids GL context issues. + webview_->SetSkipPixelReadback(true); + debugLog("CustomPlatformView: using GL texture (hardware accelerated)"); + } else { + texture_ = FL_TEXTURE(inappwebview_texture_new(webview_.get())); + debugLog("CustomPlatformView: using pixel buffer texture (software)"); + } + + if (texture_ == nullptr) { + errorLog("CustomPlatformView: failed to create texture"); + return; + } + + // Register the texture - this assigns the texture ID + gboolean registered = fl_texture_registrar_register_texture(texture_registrar_, texture_); + if (!registered) { + errorLog("CustomPlatformView: failed to register texture"); + g_object_unref(texture_); + texture_ = nullptr; + return; + } + + // Now get the texture ID after registration + texture_id_ = fl_texture_get_id(texture_); + + // Attach the webview method channel using the same id used on Dart side. + // Dart uses the returned id from createInAppWebView as both textureId and + // controller/view id. + if (webview_ != nullptr) { + webview_->AttachChannel(messenger, texture_id_); + } + + // Set up the webview's callback to mark frame available + // For EGL texture, we also update the EGL image reference + webview_->SetOnFrameAvailable([this]() { + // If using EGL texture, update the EGL image reference before marking available + if (egl_texture_ != nullptr && webview_ != nullptr) { + uint32_t width = 0; + uint32_t height = 0; + void* egl_image = webview_->GetCurrentEglImage(&width, &height); + if (egl_image != nullptr) { + inappwebview_egl_texture_set_egl_image(egl_texture_, egl_image, width, height); + } + } + MarkTextureFrameAvailable(); + }); + + // Set up cursor change callback + webview_->SetOnCursorChanged( + [this](const std::string& cursor_name) { EmitCursorChanged(cursor_name); }); + + // Create method channel for platform view operations + std::string method_channel_name = + "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + method_channel_ = + fl_method_channel_new(messenger, method_channel_name.c_str(), FL_METHOD_CODEC(codec)); + if (method_channel_ != nullptr) { + fl_method_channel_set_method_call_handler(method_channel_, HandleMethodCall, this, nullptr); + } + + // Create event channel for cursor changes, etc. + std::string event_channel_name = + "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_) + "_events"; + g_autoptr(FlStandardMethodCodec) event_codec = fl_standard_method_codec_new(); + event_channel_ = + fl_event_channel_new(messenger, event_channel_name.c_str(), FL_METHOD_CODEC(event_codec)); + if (event_channel_ != nullptr) { + fl_event_channel_set_stream_handlers(event_channel_, OnListen, OnCancel, this, nullptr); + } +} + +CustomPlatformView::~CustomPlatformView() { + debugLog("dealloc CustomPlatformView"); + + if (method_channel_ != nullptr) { + fl_method_channel_set_method_call_handler(method_channel_, nullptr, nullptr, nullptr); + g_object_unref(method_channel_); + method_channel_ = nullptr; + } + + if (event_channel_ != nullptr) { + fl_event_channel_set_stream_handlers(event_channel_, nullptr, nullptr, nullptr, nullptr); + g_object_unref(event_channel_); + event_channel_ = nullptr; + } + + if (texture_registrar_ != nullptr && texture_ != nullptr) { + fl_texture_registrar_unregister_texture(texture_registrar_, texture_); + } + + if (texture_ != nullptr) { + g_object_unref(texture_); + texture_ = nullptr; + } +} + +void CustomPlatformView::MarkTextureFrameAvailable() { + if (texture_registrar_ != nullptr && texture_ != nullptr) { + fl_texture_registrar_mark_texture_frame_available(texture_registrar_, texture_); + } +} + +void CustomPlatformView::HandleMethodCall(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + auto* self = static_cast(user_data); + self->HandleMethodCallImpl(method_call); +} + +void CustomPlatformView::HandleMethodCallImpl(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + // setSize: [double width, double height, double scaleFactor] + if (strcmp(method, "setSize") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_LIST && fl_value_get_length(args) >= 2) { + FlValue* width_value = fl_value_get_list_value(args, 0); + FlValue* height_value = fl_value_get_list_value(args, 1); + double width = 0, height = 0; + double scale_factor = 1.0; + + if (fl_value_get_type(width_value) == FL_VALUE_TYPE_FLOAT) { + width = fl_value_get_float(width_value); + } else if (fl_value_get_type(width_value) == FL_VALUE_TYPE_INT) { + width = static_cast(fl_value_get_int(width_value)); + } + + if (fl_value_get_type(height_value) == FL_VALUE_TYPE_FLOAT) { + height = fl_value_get_float(height_value); + } else if (fl_value_get_type(height_value) == FL_VALUE_TYPE_INT) { + height = static_cast(fl_value_get_int(height_value)); + } + + if (webview_ && width > 0 && height > 0) { + if (fl_value_get_length(args) >= 3) { + FlValue* scale_value = fl_value_get_list_value(args, 2); + if (scale_value != nullptr) { + if (fl_value_get_type(scale_value) == FL_VALUE_TYPE_FLOAT) { + scale_factor = fl_value_get_float(scale_value); + } else if (fl_value_get_type(scale_value) == FL_VALUE_TYPE_INT) { + scale_factor = static_cast(fl_value_get_int(scale_value)); + } + } + } + webview_->setScaleFactor(scale_factor); + // IMPORTANT: GTK/WebKit may already apply the monitor scale factor to + // offscreen rendering. Passing physical pixels here can double-scale + // the snapshot size. Keep the widget size in logical pixels and use + // scaleFactor only for input coordinate conversion. + webview_->setSize(static_cast(width), static_cast(height)); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setTextureOffset: [double x, double y] + if (strcmp(method, "setTextureOffset") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_LIST && fl_value_get_length(args) >= 2) { + FlValue* x_value = fl_value_get_list_value(args, 0); + FlValue* y_value = fl_value_get_list_value(args, 1); + double x = 0, y = 0; + + if (fl_value_get_type(x_value) == FL_VALUE_TYPE_FLOAT) { + x = fl_value_get_float(x_value); + } else if (fl_value_get_type(x_value) == FL_VALUE_TYPE_INT) { + x = static_cast(fl_value_get_int(x_value)); + } + + if (fl_value_get_type(y_value) == FL_VALUE_TYPE_FLOAT) { + y = fl_value_get_float(y_value); + } else if (fl_value_get_type(y_value) == FL_VALUE_TYPE_INT) { + y = static_cast(fl_value_get_int(y_value)); + } + + if (webview_) { + webview_->SetTextureOffset(x, y); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setCursorPos: [double x, double y] + if (strcmp(method, "setCursorPos") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_LIST && fl_value_get_length(args) >= 2) { + FlValue* x_value = fl_value_get_list_value(args, 0); + FlValue* y_value = fl_value_get_list_value(args, 1); + double x = 0, y = 0; + + if (fl_value_get_type(x_value) == FL_VALUE_TYPE_FLOAT) { + x = fl_value_get_float(x_value); + } else if (fl_value_get_type(x_value) == FL_VALUE_TYPE_INT) { + x = static_cast(fl_value_get_int(x_value)); + } + + if (fl_value_get_type(y_value) == FL_VALUE_TYPE_FLOAT) { + y = fl_value_get_float(y_value); + } else if (fl_value_get_type(y_value) == FL_VALUE_TYPE_INT) { + y = static_cast(fl_value_get_int(y_value)); + } + + if (webview_) { + webview_->SetCursorPos(x, y); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setPointerButton: {"kind": int, "button": int, "clickCount": int} + if (strcmp(method, "setPointerButton") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_MAP) { + FlValue* kind_value = fl_value_lookup_string(args, "kind"); + FlValue* button_value = fl_value_lookup_string(args, "button"); + FlValue* click_count_value = fl_value_lookup_string(args, "clickCount"); + + if (kind_value != nullptr && button_value != nullptr) { + int kind = 0, button = 0, clickCount = 1; + + if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_INT) { + kind = static_cast(fl_value_get_int(kind_value)); + } + if (fl_value_get_type(button_value) == FL_VALUE_TYPE_INT) { + button = static_cast(fl_value_get_int(button_value)); + } + if (click_count_value != nullptr && + fl_value_get_type(click_count_value) == FL_VALUE_TYPE_INT) { + clickCount = static_cast(fl_value_get_int(click_count_value)); + } + + if (webview_) { + webview_->SetPointerButton(kind, button, clickCount); + } + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setScrollDelta: [double dx, double dy] + if (strcmp(method, "setScrollDelta") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_LIST && fl_value_get_length(args) >= 2) { + FlValue* dx_value = fl_value_get_list_value(args, 0); + FlValue* dy_value = fl_value_get_list_value(args, 1); + double dx = 0, dy = 0; + + if (fl_value_get_type(dx_value) == FL_VALUE_TYPE_FLOAT) { + dx = fl_value_get_float(dx_value); + } else if (fl_value_get_type(dx_value) == FL_VALUE_TYPE_INT) { + dx = static_cast(fl_value_get_int(dx_value)); + } + + if (fl_value_get_type(dy_value) == FL_VALUE_TYPE_FLOAT) { + dy = fl_value_get_float(dy_value); + } else if (fl_value_get_type(dy_value) == FL_VALUE_TYPE_INT) { + dy = static_cast(fl_value_get_int(dy_value)); + } + + if (webview_) { + webview_->SetScrollDelta(dx, dy); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // sendKeyEvent: {"type": int, "keyCode": int64, "scanCode": int, "modifiers": int, "characters": + // string} + if (strcmp(method, "sendKeyEvent") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_MAP) { + FlValue* type_value = fl_value_lookup_string(args, "type"); + FlValue* keyCode_value = fl_value_lookup_string(args, "keyCode"); + FlValue* scanCode_value = fl_value_lookup_string(args, "scanCode"); + FlValue* modifiers_value = fl_value_lookup_string(args, "modifiers"); + FlValue* characters_value = fl_value_lookup_string(args, "characters"); + + int type = 0, scanCode = 0, modifiers = 0; + int64_t keyCode = 0; + std::string characters; + + if (type_value != nullptr && fl_value_get_type(type_value) == FL_VALUE_TYPE_INT) { + type = static_cast(fl_value_get_int(type_value)); + } + if (keyCode_value != nullptr && fl_value_get_type(keyCode_value) == FL_VALUE_TYPE_INT) { + keyCode = fl_value_get_int(keyCode_value); + } + if (scanCode_value != nullptr && fl_value_get_type(scanCode_value) == FL_VALUE_TYPE_INT) { + scanCode = static_cast(fl_value_get_int(scanCode_value)); + } + if (modifiers_value != nullptr && fl_value_get_type(modifiers_value) == FL_VALUE_TYPE_INT) { + modifiers = static_cast(fl_value_get_int(modifiers_value)); + } + if (characters_value != nullptr && + fl_value_get_type(characters_value) == FL_VALUE_TYPE_STRING) { + characters = fl_value_get_string(characters_value); + } + + if (webview_) { + webview_->SendKeyEvent(type, keyCode, scanCode, modifiers, characters); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // sendTouchEvent: {"type": int, "id": int, "x": double, "y": double, "touchPoints": list} + if (strcmp(method, "sendTouchEvent") == 0) { + if (fl_value_get_type(args) == FL_VALUE_TYPE_MAP) { + FlValue* type_value = fl_value_lookup_string(args, "type"); + FlValue* id_value = fl_value_lookup_string(args, "id"); + FlValue* x_value = fl_value_lookup_string(args, "x"); + FlValue* y_value = fl_value_lookup_string(args, "y"); + FlValue* touchPoints_value = fl_value_lookup_string(args, "touchPoints"); + + int type = 0, id = 0; + double x = 0, y = 0; + std::vector> touchPoints; + + if (type_value != nullptr && fl_value_get_type(type_value) == FL_VALUE_TYPE_INT) { + type = static_cast(fl_value_get_int(type_value)); + } + if (id_value != nullptr && fl_value_get_type(id_value) == FL_VALUE_TYPE_INT) { + id = static_cast(fl_value_get_int(id_value)); + } + if (x_value != nullptr && fl_value_get_type(x_value) == FL_VALUE_TYPE_FLOAT) { + x = fl_value_get_float(x_value); + } + if (y_value != nullptr && fl_value_get_type(y_value) == FL_VALUE_TYPE_FLOAT) { + y = fl_value_get_float(y_value); + } + + // Parse touch points list + if (touchPoints_value != nullptr && + fl_value_get_type(touchPoints_value) == FL_VALUE_TYPE_LIST) { + size_t len = fl_value_get_length(touchPoints_value); + for (size_t i = 0; i < len; i++) { + FlValue* point = fl_value_get_list_value(touchPoints_value, i); + if (fl_value_get_type(point) == FL_VALUE_TYPE_MAP) { + int point_id = 0, point_type = 0; + double point_x = 0, point_y = 0; + + FlValue* pid = fl_value_lookup_string(point, "id"); + FlValue* px = fl_value_lookup_string(point, "x"); + FlValue* py = fl_value_lookup_string(point, "y"); + FlValue* ptype = fl_value_lookup_string(point, "type"); + + if (pid && fl_value_get_type(pid) == FL_VALUE_TYPE_INT) { + point_id = static_cast(fl_value_get_int(pid)); + } + if (px && fl_value_get_type(px) == FL_VALUE_TYPE_FLOAT) { + point_x = fl_value_get_float(px); + } + if (py && fl_value_get_type(py) == FL_VALUE_TYPE_FLOAT) { + point_y = fl_value_get_float(py); + } + if (ptype && fl_value_get_type(ptype) == FL_VALUE_TYPE_INT) { + point_type = static_cast(fl_value_get_int(ptype)); + } + + touchPoints.emplace_back(point_id, point_x, point_y, point_type); + } + } + } + + if (webview_) { + webview_->SendTouchEvent(type, id, x, y, touchPoints); + } + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setFocused: bool focused + if (strcmp(method, "setFocused") == 0) { + bool focused = false; + if (fl_value_get_type(args) == FL_VALUE_TYPE_BOOL) { + focused = fl_value_get_bool(args); + } else if (fl_value_get_type(args) == FL_VALUE_TYPE_INT) { + focused = fl_value_get_int(args) != 0; + } + + if (webview_) { + webview_->setFocused(focused); + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // setVisible: bool visible + if (strcmp(method, "setVisible") == 0) { + bool visible = true; + if (fl_value_get_type(args) == FL_VALUE_TYPE_BOOL) { + visible = fl_value_get_bool(args); + } else if (fl_value_get_type(args) == FL_VALUE_TYPE_INT) { + visible = fl_value_get_int(args) != 0; + } + + if (webview_) { + webview_->setVisible(visible); + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // getActivityState: returns uint32 + if (strcmp(method, "getActivityState") == 0) { + uint32_t state = 0; + if (webview_) { + state = webview_->getActivityState(); + } + g_autoptr(FlValue) result = fl_value_new_int(static_cast(state)); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + // setTargetRefreshRate: int rate + if (strcmp(method, "setTargetRefreshRate") == 0) { + uint32_t rate = 0; + if (fl_value_get_type(args) == FL_VALUE_TYPE_INT) { + rate = static_cast(fl_value_get_int(args)); + } + + if (webview_) { + webview_->setTargetRefreshRate(rate); + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // getTargetRefreshRate: returns uint32 + if (strcmp(method, "getTargetRefreshRate") == 0) { + uint32_t rate = 0; + if (webview_) { + rate = webview_->getTargetRefreshRate(); + } + g_autoptr(FlValue) result = fl_value_new_int(static_cast(rate)); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + // requestEnterFullscreen + if (strcmp(method, "requestEnterFullscreen") == 0) { + if (webview_) { + webview_->requestEnterFullscreen(); + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // requestExitFullscreen + if (strcmp(method, "requestExitFullscreen") == 0) { + if (webview_) { + webview_->requestExitFullscreen(); + } + fl_method_call_respond_success(method_call, nullptr, nullptr); + return; + } + + // isInFullscreen: returns bool + if (strcmp(method, "isInFullscreen") == 0) { + bool fullscreen = false; + if (webview_) { + fullscreen = webview_->isInFullscreen(); + } + g_autoptr(FlValue) result = fl_value_new_bool(fullscreen); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + // requestPointerLock: returns bool + if (strcmp(method, "requestPointerLock") == 0) { + bool success = false; + if (webview_) { + success = webview_->requestPointerLock(); + } + g_autoptr(FlValue) result = fl_value_new_bool(success); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + // requestPointerUnlock: returns bool + if (strcmp(method, "requestPointerUnlock") == 0) { + bool success = false; + if (webview_) { + success = webview_->requestPointerUnlock(); + } + g_autoptr(FlValue) result = fl_value_new_bool(success); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +FlMethodErrorResponse* CustomPlatformView::OnListen(FlEventChannel* channel, FlValue* args, + gpointer user_data) { + auto* self = static_cast(user_data); + self->event_sink_active_ = true; + return nullptr; +} + +FlMethodErrorResponse* CustomPlatformView::OnCancel(FlEventChannel* channel, FlValue* args, + gpointer user_data) { + auto* self = static_cast(user_data); + self->event_sink_active_ = false; + return nullptr; +} + +void CustomPlatformView::EmitCursorChanged(const std::string& cursor_name) { + if (!event_sink_active_ || event_channel_ == nullptr) { + return; + } + + g_autoptr(FlValue) event = to_fl_map({ + {"type", make_fl_value(std::string("cursorChanged"))}, + {"value", make_fl_value(cursor_name)}, + }); + + fl_event_channel_send(event_channel_, event, nullptr, nullptr); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.h b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.h new file mode 100644 index 00000000..576f916e --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/custom_platform_view.h @@ -0,0 +1,64 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_PLATFORM_VIEW_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_PLATFORM_VIEW_H_ + +#include + +#include +#include + +#include "in_app_webview.h" +#include "inappwebview_egl_texture.h" + +namespace flutter_inappwebview_plugin { + +using WebViewType = InAppWebView; + +/// CustomPlatformView handles the method channel communication for +/// pointer/mouse events, sizing, and other platform view operations. +/// This is similar to the Windows implementation. +class CustomPlatformView { + public: + CustomPlatformView(FlBinaryMessenger* messenger, FlTextureRegistrar* texture_registrar, + std::shared_ptr webview); + ~CustomPlatformView(); + + int64_t texture_id() const { return texture_id_; } + WebViewType* webview() const { return webview_.get(); } + + // Keep-alive ID management + // When set, this WebView should be preserved when disposed and stored in keepAliveWebViews_ + void set_keep_alive_id(const std::string& id) { keepAliveId_ = id; } + const std::string& keep_alive_id() const { return keepAliveId_; } + bool has_keep_alive_id() const { return !keepAliveId_.empty(); } + + void MarkTextureFrameAvailable(); + + private: + std::shared_ptr webview_; + FlTextureRegistrar* texture_registrar_; + FlTexture* texture_ = nullptr; + InAppWebViewEGLTexture* egl_texture_ = nullptr; // Pointer to EGL texture if using zero-copy mode + int64_t texture_id_ = -1; + std::string keepAliveId_; // Keep-alive ID for preserving WebView across widget disposal + + FlMethodChannel* method_channel_ = nullptr; + FlEventChannel* event_channel_ = nullptr; + bool event_sink_active_ = false; + + // Method call handler + static void HandleMethodCall(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data); + void HandleMethodCallImpl(FlMethodCall* method_call); + + // Event channel handlers + static FlMethodErrorResponse* OnListen(FlEventChannel* channel, FlValue* args, + gpointer user_data); + static FlMethodErrorResponse* OnCancel(FlEventChannel* channel, FlValue* args, + gpointer user_data); + + void EmitCursorChanged(const std::string& cursor_name); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_PLATFORM_VIEW_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/in_app_webview/in_app_webview.cc b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/in_app_webview.cc new file mode 100644 index 00000000..95b3c806 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/in_app_webview/in_app_webview.cc @@ -0,0 +1,8001 @@ +// WPE WebKit-based InAppWebView implementation +// +// This file provides offscreen web rendering using WPE WebKit. +// Supports two backend APIs: +// - WPEPlatform (HAVE_WPE_PLATFORM): New modern API for WPE WebKit 2.40+ +// - WPEBackend-FDO (HAVE_WPE_BACKEND_LEGACY): Legacy API for older systems + +#include "in_app_webview.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Use epoxy for OpenGL/EGL instead of direct headers to avoid conflicts +#include +#include + +// WPEPlatform API (new modern API) +#ifdef HAVE_WPE_PLATFORM +#include +#include +#include // For SHM software rendering fallback +#endif + +// WPEBackend-FDO API (legacy) +#ifdef HAVE_WPE_BACKEND_LEGACY +#include +#include +#include +#endif + +// Cairo for PNG encoding (used by takeScreenshot) +#include + +#include "../plugin_scripts_js/color_input_js.h" +#include "../plugin_scripts_js/console_log_js.h" +#include "../plugin_scripts_js/cursor_detection_js.h" +#include "../plugin_scripts_js/date_input_js.h" +#include "../plugin_scripts_js/intercept_ajax_request_js.h" +#include "../plugin_scripts_js/intercept_fetch_request_js.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../plugin_scripts_js/on_load_resource_js.h" +#include "../plugin_scripts_js/print_interception_js.h" +#include "../plugin_scripts_js/web_message_channel_js.h" +#include "../plugin_scripts_js/web_message_listener_js.h" +#include "../types/client_cert_challenge.h" +#include "../types/client_cert_response.h" +#include "../types/create_window_action.h" +#include "../types/custom_scheme_response.h" +#include "../types/hit_test_result.h" +#include "../types/navigation_action.h" +#include "../types/server_trust_challenge.h" +#include "../types/web_resource_error.h" +#include "../types/web_resource_request.h" +#include "../types/web_view_transport.h" +#include "../credential_database.h" +#include "../flutter_inappwebview_linux_plugin_private.h" +#include "../plugin_instance.h" +#include "../utils/flutter.h" +#include "../utils/gl_context.h" +#include "../utils/log.h" +#include "../utils/uri.h" +#include "in_app_webview_manager.h" +#include "simd_convert.h" +#include "user_content_controller.h" +#include "webview_channel_delegate.h" +#include "../types/find_session.h" +#include "../web_message/web_message_channel.h" +#include "../web_message/web_message_listener.h" + +using json = nlohmann::json; + +// Forward declaration of InAppWebView for FileChooserContext +namespace flutter_inappwebview_plugin { +class InAppWebView; +} + +// Context data for non-blocking file chooser dialog +// Defined early because HideFileChooser() needs access to members +struct FileChooserContext { + WebKitFileChooserRequest* request; + bool selectMultiple; + flutter_inappwebview_plugin::InAppWebView* webview; // Pointer to webview for tracking active dialog + gulong response_handler_id; // Signal handler ID for cleanup + + FileChooserContext(WebKitFileChooserRequest* req, bool multi, + flutter_inappwebview_plugin::InAppWebView* wv) + : request(req), selectMultiple(multi), webview(wv), response_handler_id(0) { + g_object_ref(request); + } + + ~FileChooserContext() { + g_object_unref(request); + } +}; + +// GDK for EGL display access +#include +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif +#ifdef GDK_WINDOWING_X11 +#include +#endif + +// C-style callback functions outside the namespace for C API compatibility +#ifdef HAVE_WPE_BACKEND_LEGACY +extern "C" { +static void wpe_export_fdo_egl_image_callback(void* data, struct wpe_fdo_egl_exported_image* image); + +static void wpe_export_shm_buffer_callback(void* data, struct wpe_fdo_shm_exported_buffer* buffer); +} +#endif + +namespace flutter_inappwebview_plugin { + +namespace { + +// WPE modifier mask - matches wpe_input_modifier enum from wpe/input.h +// Control = bit 0 (1), Shift = bit 1 (2), Alt = bit 2 (4), Meta = bit 3 (8) +// These are not used directly in C++ code - modifiers come from Dart already in WPE format + +// Generate a random hex string for the JS bridge secret +std::string GenerateRandomSecret(size_t length = 32) { + static const char* hex_chars = "0123456789abcdef"; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + std::string result; + result.reserve(length); + for (size_t i = 0; i < length; ++i) { + result += hex_chars[dis(gen)]; + } + return result; +} + +} // namespace + +#ifdef HAVE_WPE_BACKEND_LEGACY +// Get the directory where the executable is located (only needed for legacy backend) +static std::string GetExecutableDir() { + char path[PATH_MAX]; + ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1); + if (len != -1) { + path[len] = '\0'; + std::string exe_path(path); + size_t last_slash = exe_path.rfind('/'); + if (last_slash != std::string::npos) { + return exe_path.substr(0, last_slash); + } + } + return ""; +} +#endif + +bool InAppWebView::IsWpeWebKitAvailable() { + static bool checked = false; + static bool available = false; + + if (checked) { + return available; + } + checked = true; + +#ifdef HAVE_WPE_PLATFORM + // WPEPlatform API: No loader initialization needed + // The platform is initialized when we create a WPEDisplay + debugLog("InAppWebView: Using WPEPlatform API (modern)"); + available = true; +#elif defined(HAVE_WPE_BACKEND_LEGACY) + // Legacy WPEBackend-FDO: Need to initialize the loader + // Try to load the WPE backend library + // First, try to load from the bundled lib/ directory using the full path. + std::string exe_dir = GetExecutableDir(); + if (!exe_dir.empty()) { + std::string bundled_lib_path = exe_dir + "/lib/libWPEBackend-fdo-1.0.so.1"; + std::string bundled_lib_dir = exe_dir + "/lib"; + + // Check if the bundled library exists + if (access(bundled_lib_path.c_str(), F_OK) == 0) { + // Set environment variables for the WPE WebProcess child process. + setenv("WPE_BACKEND_LIBRARY", bundled_lib_path.c_str(), 0); + + // Also prepend the lib directory to LD_LIBRARY_PATH + const char* current_ld_path = getenv("LD_LIBRARY_PATH"); + std::string new_ld_path = bundled_lib_dir; + if (current_ld_path != nullptr && strlen(current_ld_path) > 0) { + new_ld_path += ":"; + new_ld_path += current_ld_path; + } + setenv("LD_LIBRARY_PATH", new_ld_path.c_str(), 1); + + available = wpe_loader_init(bundled_lib_path.c_str()) != 0; + } + } + + // Fall back to system library if bundled version not found or failed to load + if (!available) { + available = wpe_loader_init("libWPEBackend-fdo-1.0.so.1") != 0; + } + + if (available) { + debugLog("InAppWebView: Using WPEBackend-FDO API (legacy)"); + } +#else + #error "Neither HAVE_WPE_PLATFORM nor HAVE_WPE_BACKEND_LEGACY is defined" +#endif + + return available; +} + +#ifdef HAVE_WPE_PLATFORM +// Check if DMA-BUF rendering should be used (called at WebView initialization) +// Returns true if DMA-BUF rendering is expected to work +// Note: The actual environment detection and LIBGL_ALWAYS_SOFTWARE setting +// is now done at plugin registration time via utils/software_rendering.h +bool InAppWebView::PreflightDmaBufSupport() { + const char* sw_env = getenv("LIBGL_ALWAYS_SOFTWARE"); + if (sw_env && (strcmp(sw_env, "1") == 0 || strcasecmp(sw_env, "true") == 0)) { + return false; // Software rendering mode + } + return true; // Hardware rendering mode +} +#endif + +InAppWebView::InAppWebView(FlPluginRegistrar* registrar, FlBinaryMessenger* messenger, int64_t id, + const InAppWebViewCreationParams& params) + : plugin_(params.plugin), registrar_(registrar), messenger_(messenger), gtk_window_(params.gtkWindow), fl_view_(params.flView), manager_(params.manager), id_(id), settings_(params.initialSettings), + initial_user_scripts_(params.initialUserScripts) { + js_bridge_secret_ = GenerateRandomSecret(); + + if (params.windowId.has_value()) { + window_id_ = params.windowId.value(); + } + + if (params.contextMenu.has_value()) { + context_menu_config_ = params.contextMenu.value(); + } + + InitWpeBackend(); + InitWebView(params); + RegisterEventHandlers(); + + // Set up monitor change handlers and initial refresh rate (like Cog browser does) + // This helps WPE synchronize frame production with the display + SetupMonitorChangeHandlers(); + UpdateMonitorRefreshRate(); + + if (settings_) { + settings_->applyToWebView(webview_); +#ifdef HAVE_WPE_PLATFORM + if (wpe_display_ != nullptr) { + settings_->applyWpePlatformSettings(wpe_display_); + } +#endif + } + + RegisterCustomSchemes(); + + // Apply content blockers if specified, then load initial content + // Content blockers must be compiled asynchronously + if (settings_ && settings_->contentBlockers != nullptr && content_blocker_handler_) { + // Store initial load params for callback + auto initialUrlRequest = params.initialUrlRequest; + auto initialData = params.initialData; + auto initialDataMimeType = params.initialDataMimeType; + auto initialDataEncoding = params.initialDataEncoding; + auto initialDataBaseUrl = params.initialDataBaseUrl; + auto initialFile = params.initialFile; + + content_blocker_handler_->setContentBlockers( + settings_->contentBlockers, + [this, initialUrlRequest, initialData, initialDataMimeType, initialDataEncoding, + initialDataBaseUrl, initialFile](bool success) { + if (!success) { + debugLog("InAppWebView: Content blockers failed to apply, loading content anyway"); + } + + // Now load initial content + if (initialUrlRequest.has_value()) { + loadUrl(initialUrlRequest.value()); + } else if (initialData.has_value()) { + std::string mimeType = initialDataMimeType.value_or("text/html"); + std::string encoding = initialDataEncoding.value_or("UTF-8"); + std::string baseUrl = initialDataBaseUrl.value_or("about:blank"); + loadData(initialData.value(), mimeType, encoding, baseUrl); + } else if (initialFile.has_value()) { + loadFile(initialFile.value()); + } + }); + } else { + // No content blockers - load initial content immediately + if (params.initialUrlRequest.has_value()) { + auto& urlRequest = params.initialUrlRequest.value(); + loadUrl(urlRequest); + } else if (params.initialData.has_value()) { + std::string mimeType = params.initialDataMimeType.value_or("text/html"); + std::string encoding = params.initialDataEncoding.value_or("UTF-8"); + std::string baseUrl = params.initialDataBaseUrl.value_or("about:blank"); + loadData(params.initialData.value(), mimeType, encoding, baseUrl); + } else if (params.initialFile.has_value()) { + loadFile(params.initialFile.value()); + } + } +} + +void InAppWebView::AttachChannel(FlBinaryMessenger* messenger, int64_t channel_id) { + channel_id_ = channel_id; + if (messenger == nullptr) { + errorLog("InAppWebView: AttachChannel messenger is null"); + return; + } + + std::string channelName = std::string(METHOD_CHANNEL_NAME_PREFIX) + std::to_string(channel_id_); + channel_delegate_ = std::make_unique(this, messenger, channelName); + + if (findInteractionController_) { + findInteractionController_->attachChannel(messenger, std::to_string(channel_id_)); + } +} + +void InAppWebView::AttachChannel(FlBinaryMessenger* messenger, const std::string& channel_id, const bool is_full_channel_name) { + string_channel_id_ = channel_id; + if (messenger == nullptr) { + errorLog("InAppWebView: AttachChannel messenger is null"); + return; + } + + // Determine the channel name: + // - If is_full_channel_name (InAppBrowser case), use it directly + // - If channel_id is just an ID (HeadlessInAppWebView case), prepend the prefix + std::string channelName; + if (is_full_channel_name) { + // Already has the prefix (InAppBrowser passes full channel name) + channelName = channel_id; + } else { + // Just an ID, prepend the prefix (HeadlessInAppWebView case) + channelName = std::string(METHOD_CHANNEL_NAME_PREFIX) + channel_id; + } + channel_delegate_ = std::make_unique(this, messenger, channelName); + + if (findInteractionController_) { + findInteractionController_->attachChannel(messenger, channel_id); + } +} + +InAppWebView::~InAppWebView() { + debugLog("dealloc InAppWebView"); + + CleanupMonitorChangeHandlers(); + + context_menu_popup_.reset(); + + if (findInteractionController_) { + findInteractionController_->dispose(); + findInteractionController_.reset(); + } + + if (pending_context_menu_ != nullptr) { + g_object_unref(pending_context_menu_); + pending_context_menu_ = nullptr; + } + if (pending_hit_test_result_ != nullptr) { + g_object_unref(pending_hit_test_result_); + pending_hit_test_result_ = nullptr; + } + + if (last_hit_test_result_ != nullptr) { + g_object_unref(last_hit_test_result_); + last_hit_test_result_ = nullptr; + } + + // Disconnect download-started signal from NetworkSession before destroying webview + if (webview_ != nullptr && download_started_handler_id_ != 0) { + WebKitNetworkSession* network_session = webkit_web_view_get_network_session(webview_); + if (network_session != nullptr) { + g_signal_handler_disconnect(network_session, download_started_handler_id_); + } + download_started_handler_id_ = 0; + } + + for (auto& pair : web_message_channels_) { + if (pair.second) { + pair.second->dispose(); + } + } + web_message_channels_.clear(); + + for (auto& pair : web_message_listeners_) { + if (pair.second) { + pair.second->dispose(); + } + } + web_message_listeners_.clear(); + + // IMPORTANT: Clean up user content controller FIRST while webview is still valid + // The UserContentController destructor needs access to WebKit's user content manager + // which becomes invalid after we unref the webview + user_content_controller_.reset(); + + // IMPORTANT: Clean up content blocker handler BEFORE the webview is destroyed + // The ContentBlockerHandler destructor calls removeAllFilters which needs a valid content manager + content_blocker_handler_.reset(); + + for (auto& pair : pending_policy_decisions_) { + webkit_policy_decision_ignore(pair.second); + g_object_unref(pair.second); + } + pending_policy_decisions_.clear(); + +#ifdef HAVE_WPE_PLATFORM + // === WPEPlatform proper shutdown sequence === + // Mark as disposing to prevent buffer callbacks from processing + is_disposing_.store(true); + + // 1. First disconnect signals to stop receiving callbacks + if (wpe_view_ != nullptr && buffer_rendered_handler_ != 0) { + g_signal_handler_disconnect(wpe_view_, buffer_rendered_handler_); + buffer_rendered_handler_ = 0; + } + // Note: scale_changed_handler_ is connected to gtk_window_, not wpe_view_ + if (gtk_window_ != nullptr && scale_changed_handler_ != 0) { + g_signal_handler_disconnect(gtk_window_, scale_changed_handler_); + scale_changed_handler_ = 0; + } + + if (wpe_view_ != nullptr) { + wpe_view_focus_out(wpe_view_); + } + + if (wpe_view_ != nullptr) { + wpe_view_unmap(wpe_view_); + } + + // 4. Release any pending buffer back to WPE and clean up EGL image + { + std::lock_guard lock(wpe_buffer_mutex_); + + // Clean up EGL image first (while display is still valid) + if (current_egl_image_ != nullptr && egl_display_ != nullptr) { + static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; + if (eglDestroyImageKHR == nullptr) { + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + } + if (eglDestroyImageKHR != nullptr) { + eglDestroyImageKHR(static_cast(egl_display_), + static_cast(current_egl_image_)); + } + current_egl_image_ = nullptr; + } + + // Release pending buffer back to WPE + if (current_buffer_ != nullptr && wpe_view_ != nullptr) { + wpe_view_buffer_released(wpe_view_, current_buffer_); + } + current_buffer_ = nullptr; + current_buffer_width_ = 0; + current_buffer_height_ = 0; + } + + if (wpe_view_ != nullptr) { + wpe_view_closed(wpe_view_); + } + + wpe_view_ = nullptr; + wpe_toplevel_ = nullptr; + + if (webview_ != nullptr) { + g_object_unref(webview_); + webview_ = nullptr; + } + + if (wpe_display_ != nullptr) { + g_object_unref(wpe_display_); + wpe_display_ = nullptr; + } + + egl_display_ = nullptr; +#elif defined(HAVE_WPE_BACKEND_LEGACY) + { + std::lock_guard lock(exported_image_mutex_); + if (exported_image_ != nullptr && exportable_ != nullptr) { + ::wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(exportable_, + exported_image_); + exported_image_ = nullptr; + } + } + + if (webview_ != nullptr) { + g_object_unref(webview_); + webview_ = nullptr; + } +#endif +} + +void InAppWebView::InitWpeBackend() { + if (!IsWpeWebKitAvailable()) { + errorLog("InAppWebView: WPE WebKit not available"); + return; + } + +#ifdef HAVE_WPE_PLATFORM + // === WPEPlatform API (Modern) === + + // NOTE: The DMA-BUF preflight check and LIBGL_ALWAYS_SOFTWARE setup + // is now done at plugin registration time via RunEarlyPreflightCheck(). + // This ensures the environment is set BEFORE any WPEDisplay is created. + + // Create a headless display for offscreen rendering + GError* error = nullptr; + + wpe_display_ = wpe_display_headless_new(); + if (wpe_display_ == nullptr) { + errorLog("InAppWebView: Failed to create WPEDisplayHeadless"); + return; + } + + // Connect the display + if (!wpe_display_connect(wpe_display_, &error)) { + errorLog("InAppWebView: Failed to connect WPEDisplay: " + + std::string(error ? error->message : "unknown")); + g_clear_error(&error); + g_clear_object(&wpe_display_); + return; + } + + // Get EGL display from WPEDisplay for texture operations + egl_display_ = wpe_display_get_egl_display(wpe_display_, &error); + if (egl_display_ == nullptr) { + // Software rendering mode - no EGL display available + g_clear_error(&error); + } + + // Note: The WebView will be created in InitWebView() using the "display" property + // WPEView and WPEToplevel are obtained from the WebView after creation + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + // === WPEBackend-FDO API (Legacy) === + + // Get EGL display from GDK + EGLDisplay egl_display = EGL_NO_DISPLAY; + GdkDisplay* gdk_display = gdk_display_get_default(); + + if (gdk_display != nullptr) { +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY(gdk_display)) { + struct wl_display* wl_display = gdk_wayland_display_get_wl_display(gdk_display); + if (wl_display != nullptr) { + egl_display = eglGetDisplay((EGLNativeDisplayType)wl_display); + } + } +#endif +#ifdef GDK_WINDOWING_X11 + if (egl_display == EGL_NO_DISPLAY && GDK_IS_X11_DISPLAY(gdk_display)) { + Display* x11_display = gdk_x11_display_get_xdisplay(gdk_display); + if (x11_display != nullptr) { + egl_display = eglGetDisplay((EGLNativeDisplayType)x11_display); + } + } +#endif + } + + // If we couldn't get an EGL display, try the default + if (egl_display == EGL_NO_DISPLAY) { + egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + } + + // Initialize EGL if needed + if (egl_display != EGL_NO_DISPLAY) { + EGLint major, minor; + if (!eglInitialize(egl_display, &major, &minor)) { + errorLog("InAppWebView: Failed to initialize EGL"); + egl_display = EGL_NO_DISPLAY; + } + } + + egl_display_ = egl_display; + + // Initialize WPE FDO with the EGL display + if (!wpe_fdo_initialize_for_egl_display(egl_display)) { + errorLog("InAppWebView: Failed to initialize WPE FDO"); + // Try again with headless mode + if (!wpe_fdo_initialize_for_egl_display(EGL_NO_DISPLAY)) { + errorLog("InAppWebView: Failed to initialize WPE FDO in headless mode"); + } + } + + // Create the exportable backend for DMA-BUF export + static struct wpe_view_backend_exportable_fdo_egl_client exportable_client = { + nullptr, // export_egl_image callback (legacy) + wpe_export_fdo_egl_image_callback, // export_fdo_egl_image callback + wpe_export_shm_buffer_callback, // export_shm_buffer callback + nullptr, nullptr // reserved + }; + + exportable_ = + wpe_view_backend_exportable_fdo_egl_create(&exportable_client, this, width_, height_); + + if (exportable_ == nullptr) { + errorLog("InAppWebView: Failed to create WPE exportable backend"); + return; + } + + wpe_backend_ = wpe_view_backend_exportable_fdo_get_view_backend(exportable_); + + // Create WebKit backend wrapper + backend_ = webkit_web_view_backend_new( + wpe_backend_, + [](gpointer data) { + auto* exportable = static_cast(data); + wpe_view_backend_exportable_fdo_destroy(exportable); + }, + exportable_); + + wpe_view_backend_dispatch_set_device_scale_factor(wpe_backend_, scale_factor_); + + // Set initial activity state + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_visible); + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_in_window); + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_focused); + + // Set up fullscreen handler for DOM fullscreen requests + wpe_view_backend_set_fullscreen_handler( + wpe_backend_, + [](void* data, bool fullscreen) -> bool { + auto* self = static_cast(data); + return self->OnDomFullscreenRequest(fullscreen); + }, + this); + + // Set up pointer lock handler for games/immersive applications + wpe_view_backend_set_pointer_lock_handler( + wpe_backend_, + [](void* data, bool lock) -> bool { + auto* self = static_cast(data); + return self->OnPointerLockRequest(lock); + }, + this); +#endif +} + +void InAppWebView::InitWebView(const InAppWebViewCreationParams& params) { +#ifdef HAVE_WPE_PLATFORM + // === WPEPlatform API === + // With WPEPlatform, we pass the "display" property to create the WebView + // The WPEView is automatically created by WebKit + + if (wpe_display_ == nullptr) { + errorLog("InAppWebView: Cannot create webview without WPEDisplay"); + return; + } + + // Create WebKit settings + WebKitSettings* settings = webkit_settings_new(); + + bool useIncognito = params.initialSettings && params.initialSettings->incognito; + WebKitNetworkSession* networkSession = nullptr; + + if (useIncognito) { + networkSession = webkit_network_session_new_ephemeral(); + debugLog("InAppWebView: Creating WebView with ephemeral (incognito) network session"); + } + + WebKitWebContext* webContext = params.webContext; + + // Check if we're creating a related webview (for multi-window support) + if (params.relatedWebView != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "display", wpe_display_, + "user-content-manager", webkit_web_view_get_user_content_manager(params.relatedWebView), + "settings", webkit_web_view_get_settings(params.relatedWebView), + "related-view", params.relatedWebView, + nullptr)); + } else if (webContext != nullptr) { + if (networkSession != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "display", wpe_display_, + "web-context", webContext, + "network-session", networkSession, + "settings", settings, + nullptr)); + } else { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "display", wpe_display_, + "web-context", webContext, + "settings", settings, + nullptr)); + } + } else if (networkSession != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "display", wpe_display_, + "network-session", networkSession, + "settings", settings, + nullptr)); + } else { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "display", wpe_display_, + "settings", settings, + nullptr)); + } + + g_object_unref(settings); + + if (webview_ == nullptr) { + errorLog("InAppWebView: Failed to create WebKitWebView with WPEPlatform"); + if (networkSession != nullptr) { + g_object_unref(networkSession); + } + return; + } + + // Get WPEView from the WebView (created automatically by WebKit) + wpe_view_ = webkit_web_view_get_wpe_view(webview_); + if (wpe_view_ == nullptr) { + errorLog("InAppWebView: Failed to get WPEView from WebView"); + g_object_unref(webview_); + webview_ = nullptr; + return; + } + + // Note: Scale factor in WPEPlatform is read from the display, not set directly + // The WPEDisplay handles scale factor automatically based on the output + + // IMPORTANT: Connect to buffer-rendered signal BEFORE mapping the view + // This ensures we don't miss the first frame that WPE renders after mapping + buffer_rendered_handler_ = g_signal_connect(wpe_view_, "buffer-rendered", + G_CALLBACK(+[](WPEView* view, WPEBuffer* buffer, gpointer user_data) { + auto* self = static_cast(user_data); + self->OnWpePlatformBufferRendered(buffer); + }), this); + + // Get toplevel for size management (need this before setting scale) + wpe_toplevel_ = wpe_view_get_toplevel(wpe_view_); + + // WPEDisplayHeadless doesn't track real display scale, so we need to get it from GTK + // and manually notify WPE when it changes + if (gtk_window_ != nullptr) { + // Get initial scale from GTK window (which tracks the actual display scale) + int gtk_scale = gtk_widget_get_scale_factor(GTK_WIDGET(gtk_window_)); + if (gtk_scale > 0 && static_cast(gtk_scale) != scale_factor_) { + scale_factor_ = static_cast(gtk_scale); + // Notify WPE about the real display scale + if (wpe_toplevel_ != nullptr) { + wpe_toplevel_scale_changed(wpe_toplevel_, scale_factor_); + } + } + + // Connect to GTK window's scale-factor changes (triggered for example by Ubuntu display settings) + scale_changed_handler_ = g_signal_connect(gtk_window_, "notify::scale-factor", + G_CALLBACK(+[](GObject* object, GParamSpec* pspec, gpointer user_data) { + auto* self = static_cast(user_data); + auto* widget = GTK_WIDGET(object); + int new_scale = gtk_widget_get_scale_factor(widget); + + if (new_scale > 0 && static_cast(new_scale) != self->scale_factor_) { + self->scale_factor_ = static_cast(new_scale); + + // Notify WPE about the scale change so it renders at the correct resolution + if (self->wpe_toplevel_ != nullptr) { + wpe_toplevel_scale_changed(self->wpe_toplevel_, self->scale_factor_); + } + + // Notify Flutter that dimensions may have changed + if (self->on_frame_available_) { + self->on_frame_available_(); + } + } + }), this); + } else { + debugLog("Warning: No GTK window available for scale detection"); + } + + // Map the view to start rendering + wpe_view_map(wpe_view_); + + // Set focus so the view starts rendering and receiving input + wpe_view_focus_in(wpe_view_); + + // Resize toplevel (already obtained earlier for scale setup) + if (wpe_toplevel_ != nullptr) { + wpe_toplevel_resize(wpe_toplevel_, width_, height_); + } + + // Apply ITP setting if configured + if (params.initialSettings != nullptr) { + WebKitNetworkSession* session = webkit_web_view_get_network_session(webview_); + if (session != nullptr && params.initialSettings->itpEnabled) { + webkit_network_session_set_itp_enabled(session, TRUE); + } + } + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + // === WPEBackend-FDO API (Legacy) === + + if (backend_ == nullptr) { + errorLog("InAppWebView: Cannot create webview without backend"); + return; + } + + // Check if we're creating a related webview (for multi-window support) + if (params.relatedWebView != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "backend", backend_, + "user-content-manager", webkit_web_view_get_user_content_manager(params.relatedWebView), + "settings", webkit_web_view_get_settings(params.relatedWebView), + "related-view", params.relatedWebView, + nullptr)); + + if (webview_ == nullptr) { + errorLog("InAppWebView: Failed to create related WebKitWebView"); + return; + } + } else { + // Create WebKit settings for a standalone webview + WebKitSettings* settings = webkit_settings_new(); + + // Check if incognito mode is enabled + bool useIncognito = params.initialSettings && params.initialSettings->incognito; + WebKitNetworkSession* networkSession = nullptr; + + if (useIncognito) { + networkSession = webkit_network_session_new_ephemeral(); + debugLog("InAppWebView: Creating WebView with ephemeral (incognito) network session"); + } + + // Check if a custom WebKitWebContext is provided + WebKitWebContext* webContext = params.webContext; + + if (webContext != nullptr) { + debugLog("InAppWebView: Creating WebView with custom WebKitWebContext"); + + if (networkSession != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "backend", backend_, + "web-context", webContext, + "network-session", networkSession, + nullptr)); + } else { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "backend", backend_, + "web-context", webContext, + nullptr)); + } + } else if (networkSession != nullptr) { + webview_ = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "backend", backend_, + "network-session", networkSession, + nullptr)); + } else { + webview_ = webkit_web_view_new(backend_); + } + + if (webview_ == nullptr) { + errorLog("InAppWebView: Failed to create WebKitWebView"); + if (settings != nullptr) { + g_object_unref(settings); + } + if (networkSession != nullptr) { + g_object_unref(networkSession); + } + return; + } + + if (params.initialSettings != nullptr) { + WebKitNetworkSession* session = webkit_web_view_get_network_session(webview_); + if (session != nullptr && params.initialSettings->itpEnabled) { + webkit_network_session_set_itp_enabled(session, TRUE); + debugLog("InAppWebView: ITP enabled"); + } + } + + webkit_web_view_set_settings(webview_, settings); + g_object_unref(settings); + } +#endif + + // === Common initialization (both APIs) === + + WebKitColor bg = {1.0, 1.0, 1.0, 1.0}; + webkit_web_view_set_background_color(webview_, &bg); + + user_content_controller_ = std::make_unique(webview_); + + findInteractionController_ = std::make_unique(this); + + // Create content blocker handler for Safari-style content blocking rules + WebKitUserContentManager* content_manager = webkit_web_view_get_user_content_manager(webview_); + if (content_manager != nullptr) { + content_blocker_handler_ = std::make_unique(content_manager); + } +} + +void InAppWebView::RegisterEventHandlers() { + if (webview_ == nullptr) { + return; + } + + // Set up the script message handler callback + if (user_content_controller_) { + // Set up handler callback for the callHandler message handler + // The registration is done via messageHandlerNames in plugin scripts (javascript_bridge_js.h) + // This uses the with_reply API for proper Promise resolution in iframes + user_content_controller_->setScriptMessageWithReplyHandler("callHandler", + [this](const std::string& body, WebKitScriptMessageReply* reply) -> bool { + return handleScriptMessageWithReply(body, reply); + }); + + // Add plugin scripts based on settings + // The plugin scripts register their message handlers via messageHandlerNames + PrepareAndAddUserScripts(); + } + + g_signal_connect(webview_, "load-changed", G_CALLBACK(OnLoadChanged), this); + g_signal_connect(webview_, "decide-policy", G_CALLBACK(OnDecidePolicy), this); + g_signal_connect(webview_, "notify::estimated-load-progress", + G_CALLBACK(OnNotifyEstimatedLoadProgress), this); + g_signal_connect(webview_, "notify::title", G_CALLBACK(OnNotifyTitle), this); + g_signal_connect(webview_, "notify::uri", G_CALLBACK(OnNotifyUri), this); + g_signal_connect(webview_, "load-failed", G_CALLBACK(OnLoadFailed), this); + g_signal_connect(webview_, "load-failed-with-tls-errors", G_CALLBACK(OnLoadFailedWithTlsErrors), + this); + g_signal_connect(webview_, "close", G_CALLBACK(OnCloseRequest), this); + g_signal_connect(webview_, "script-dialog", G_CALLBACK(OnScriptDialog), this); + g_signal_connect(webview_, "permission-request", G_CALLBACK(OnPermissionRequest), this); + g_signal_connect(webview_, "authenticate", G_CALLBACK(OnAuthenticate), this); + g_signal_connect(webview_, "context-menu", G_CALLBACK(OnContextMenu), this); + g_signal_connect(webview_, "context-menu-dismissed", G_CALLBACK(OnContextMenuDismissed), this); + g_signal_connect(webview_, "enter-fullscreen", G_CALLBACK(OnEnterFullscreen), this); + g_signal_connect(webview_, "leave-fullscreen", G_CALLBACK(OnLeaveFullscreen), this); + g_signal_connect(webview_, "mouse-target-changed", G_CALLBACK(OnMouseTargetChanged), this); + g_signal_connect(webview_, "create", G_CALLBACK(OnCreateWebView), this); + g_signal_connect(webview_, "web-process-terminated", G_CALLBACK(OnWebProcessTerminated), this); + g_signal_connect(webview_, "run-file-chooser", G_CALLBACK(OnRunFileChooser), this); + g_signal_connect(webview_, "show-option-menu", G_CALLBACK(OnShowOptionMenu), this); + + // Connect to download-started signal on NetworkSession (WPE WebKit 2.40+ API) + // Note: In WPE WebKit, download-started is on NetworkSession, not WebView + WebKitNetworkSession* network_session = webkit_web_view_get_network_session(webview_); + if (network_session != nullptr) { + download_started_handler_id_ = g_signal_connect(network_session, "download-started", G_CALLBACK(OnDownloadStarted), this); + } + + // Connect to back-forward-list changed signal for navigation state updates + // This enables InAppBrowser back/forward button state tracking + WebKitBackForwardList* bfList = webkit_web_view_get_back_forward_list(webview_); + if (bfList != nullptr) { + g_signal_connect(bfList, "changed", G_CALLBACK(OnBackForwardListChanged), this); + } + + // Connect to notify::camera-capture-state signal for onCameraCaptureStateChanged + // Available since WPE WebKit 2.34 + g_signal_connect(webview_, "notify::camera-capture-state", + G_CALLBACK(OnNotifyCameraCaptureState), this); + + // Connect to notify::microphone-capture-state signal for onMicrophoneCaptureStateChanged + // Available since WPE WebKit 2.34 + g_signal_connect(webview_, "notify::microphone-capture-state", + G_CALLBACK(OnNotifyMicrophoneCaptureState), this); + + webkit_web_view_add_frame_displayed_callback( + webview_, + [](WebKitWebView*, gpointer data) { + auto* self = static_cast(data); + self->OnFrameDisplayed(data); + }, + this, nullptr); +} + +void InAppWebView::PrepareAndAddUserScripts() { + if (user_content_controller_ == nullptr) { + return; + } + + bool javaScriptBridgeEnabled = java_script_bridge_enabled_; + if (settings_) { + javaScriptBridgeEnabled = settings_->javaScriptBridgeEnabled; + } + + if (!javaScriptBridgeEnabled) { + return; + } + + // Get plugin scripts settings + std::optional> pluginScriptsOriginAllowList = std::nullopt; + bool pluginScriptsForMainFrameOnly = false; + + if (settings_) { + pluginScriptsOriginAllowList = settings_->pluginScriptsOriginAllowList; + pluginScriptsForMainFrameOnly = settings_->pluginScriptsForMainFrameOnly; + } + + // Get JavaScript bridge-specific settings + // If javaScriptBridgeOriginAllowList is not set, fall back to pluginScriptsOriginAllowList + std::optional> javaScriptBridgeOriginAllowList = pluginScriptsOriginAllowList; + bool javaScriptBridgeForMainFrameOnly = pluginScriptsForMainFrameOnly; + + if (settings_) { + if (settings_->javaScriptBridgeOriginAllowList.has_value()) { + javaScriptBridgeOriginAllowList = settings_->javaScriptBridgeOriginAllowList; + } + if (settings_->javaScriptBridgeForMainFrameOnly.has_value()) { + javaScriptBridgeForMainFrameOnly = settings_->javaScriptBridgeForMainFrameOnly.value(); + } + } + + // === Add JavaScript Bridge Plugin Script === + // This is the core bridge for communication between web content and native code + auto jsBridgeScript = JavaScriptBridgeJS::JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT( + js_bridge_secret_, javaScriptBridgeOriginAllowList, javaScriptBridgeForMainFrameOnly); + user_content_controller_->addPluginScript(std::move(jsBridgeScript)); + + // === Add Console Log Interception Script === + // Note: Console log is always for main frame only to avoid issues + // (see https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) + auto consoleLogScript = ConsoleLogJS::CONSOLE_LOG_JS_PLUGIN_SCRIPT(pluginScriptsOriginAllowList); + user_content_controller_->addPluginScript(std::move(consoleLogScript)); + + // === Add Color Input Interception Script === + // WPE WebKit doesn't have the run-color-chooser signal, so we handle + // via JavaScript interception + auto colorInputScript = + ColorInputJS::COLOR_INPUT_JS_PLUGIN_SCRIPT(pluginScriptsOriginAllowList, pluginScriptsForMainFrameOnly); + user_content_controller_->addPluginScript(std::move(colorInputScript)); + + // === Add Date Input Interception Script === + // WPE WebKit doesn't have date picker support, so we handle addPluginScript(std::move(cursorDetectionScript)); + + // === Add OnLoadResource Script === + // Uses PerformanceObserver API to track resource loading + if (settings_ != nullptr && settings_->useOnLoadResource) { + auto onLoadResourceScript = OnLoadResourceJS::ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT( + pluginScriptsOriginAllowList, pluginScriptsForMainFrameOnly); + user_content_controller_->addPluginScript(std::move(onLoadResourceScript)); + } + + // === Add AJAX Request Interception Script === + // Intercepts XMLHttpRequest calls for shouldInterceptAjaxRequest, onAjaxReadyStateChange, onAjaxProgress + if (settings_ != nullptr && settings_->useShouldInterceptAjaxRequest) { + auto ajaxInterceptScript = InterceptAjaxRequestJS::INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT( + pluginScriptsOriginAllowList, + pluginScriptsForMainFrameOnly, + settings_->useOnAjaxReadyStateChange, + settings_->useOnAjaxProgress); + user_content_controller_->addPluginScript(std::move(ajaxInterceptScript)); + } + + // === Add Fetch Request Interception Script === + // Intercepts fetch() calls for shouldInterceptFetchRequest + if (settings_ != nullptr && settings_->useShouldInterceptFetchRequest) { + auto fetchInterceptScript = InterceptFetchRequestJS::INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT( + pluginScriptsOriginAllowList, pluginScriptsForMainFrameOnly); + user_content_controller_->addPluginScript(std::move(fetchInterceptScript)); + } + + // === Add Print Interception Script === + // WPE WebKit doesn't have a native print signal, so we intercept window.print() + // via JavaScript and notify the Dart side + auto printInterceptionScript = PrintInterceptionJS::PRINT_INTERCEPTION_JS_PLUGIN_SCRIPT( + pluginScriptsOriginAllowList, pluginScriptsForMainFrameOnly); + user_content_controller_->addPluginScript(std::move(printInterceptionScript)); + + // TODO: Add additional plugin scripts as needed: + // - FindTextHighlightJS + // - etc. + + // === Add Initial User Scripts === + // These are scripts passed via initialUserScripts parameter from Dart + for (const auto& userScript : initial_user_scripts_) { + user_content_controller_->addUserScript(userScript); + } +} + +// === Monitor Change Handlers === + +void InAppWebView::SetupMonitorChangeHandlers() { + if (registrar_ == nullptr) { + return; + } + + // Get the GdkDisplay to connect to monitors-changed signal + GdkDisplay* display = gdk_display_get_default(); + if (display != nullptr) { + // Connect to monitors-changed signal on the display + // This fires when monitors are added, removed, or their properties change + monitors_changed_handler_id_ = g_signal_connect( + display, "monitor-added", + G_CALLBACK(+[](GdkDisplay*, GdkMonitor*, gpointer user_data) { + auto* self = static_cast(user_data); + self->UpdateMonitorRefreshRate(); + }), + this); + + // Also connect to monitor-removed in case the window moves to another monitor + g_signal_connect( + display, "monitor-removed", + G_CALLBACK(+[](GdkDisplay*, GdkMonitor*, gpointer user_data) { + auto* self = static_cast(user_data); + self->UpdateMonitorRefreshRate(); + }), + this); + } + + // Connect to configure-event on the toplevel window to detect window moves/resizes + // This helps us detect when the window moves between monitors + if (gtk_window_ != nullptr) { + configure_event_handler_id_ = g_signal_connect( + gtk_window_, "configure-event", + G_CALLBACK(+[](GtkWidget*, GdkEventConfigure*, gpointer user_data) -> gboolean { + auto* self = static_cast(user_data); + self->UpdateMonitorRefreshRate(); + return FALSE; // Continue event propagation + }), + this); + } +} + +void InAppWebView::CleanupMonitorChangeHandlers() { + // Disconnect monitors-changed signal + if (monitors_changed_handler_id_ != 0) { + GdkDisplay* display = gdk_display_get_default(); + if (display != nullptr) { + g_signal_handler_disconnect(display, monitors_changed_handler_id_); + } + monitors_changed_handler_id_ = 0; + } + + // Disconnect configure-event signal + if (configure_event_handler_id_ != 0 && gtk_window_ != nullptr) { + g_signal_handler_disconnect(gtk_window_, configure_event_handler_id_); + configure_event_handler_id_ = 0; + } +} + +void InAppWebView::UpdateMonitorRefreshRate() { +#ifdef HAVE_WPE_BACKEND_LEGACY + if (gtk_window_ == nullptr || wpe_backend_ == nullptr) { + return; + } + + int refresh_rate_mhz = flutter_inappwebview_linux_plugin_get_monitor_refresh_rate_for_window(gtk_window_); + if (refresh_rate_mhz > 0) { + uint32_t new_rate = static_cast(refresh_rate_mhz); + // Only update if the rate has actually changed + if (new_rate != target_refresh_rate_) { + wpe_view_backend_set_target_refresh_rate(wpe_backend_, new_rate); + target_refresh_rate_ = new_rate; + } + } +#endif +} + +// === WPE Backend Callbacks === + +void InAppWebView::OnFrameDisplayed(void* data) { + auto* self = static_cast(data); + + if (self->on_frame_available_) { + self->on_frame_available_(); + } +} + +#ifdef HAVE_WPE_BACKEND_LEGACY +void InAppWebView::OnExportDmaBuf(::wpe_fdo_egl_exported_image* image) { + if (image == nullptr) { + return; + } + + uint32_t img_width = wpe_fdo_egl_exported_image_get_width(image); + uint32_t img_height = wpe_fdo_egl_exported_image_get_height(image); + + // Get the EGL image from the exported image + EGLImageKHR egl_image = wpe_fdo_egl_exported_image_get_egl_image(image); + + // Only do pixel readback if: + // 1. skip_pixel_readback_ is false (not using zero-copy mode) + // 2. egl_display_ is available + // 3. We have a valid EGL image + if (!skip_pixel_readback_ && egl_image != EGL_NO_IMAGE_KHR && egl_display_ != nullptr) { + ReadPixelsFromEglImage(egl_image, img_width, img_height); + } + + // Protect exported_image_ access - this method is called from WPE's thread + // while GetCurrentEglImage may be called from Flutter's rendering thread + { + std::lock_guard lock(exported_image_mutex_); + + // Release previous exported image + if (exported_image_ != nullptr && exportable_ != nullptr) { + ::wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(exportable_, + exported_image_); + } + + exported_image_ = image; + } + + // Call on_frame_available BEFORE dispatch_frame_complete + // This ensures the EGL image is captured before we signal WPE we're ready for more + if (on_frame_available_) { + on_frame_available_(); + } + + // Dispatch frame complete to allow WebKit to render next frame + // Note: This is moved AFTER on_frame_available to ensure the EGL image is used first + if (exportable_ != nullptr) { + wpe_view_backend_exportable_fdo_dispatch_frame_complete(exportable_); + } +} +#endif + +#ifdef HAVE_WPE_PLATFORM +void InAppWebView::OnWpePlatformBufferRendered(WPEBuffer* buffer) { + if (buffer == nullptr) { + return; + } + + // Don't process buffers during destruction + if (is_disposing_.load()) { + // Still need to release the buffer back to WPE + if (wpe_view_ != nullptr) { + wpe_view_buffer_released(wpe_view_, buffer); + } + return; + } + + // Get buffer dimensions + uint32_t buf_width = static_cast(wpe_buffer_get_width(buffer)); + uint32_t buf_height = static_cast(wpe_buffer_get_height(buffer)); + + + WPEBuffer* previous_buffer = nullptr; + bool buffer_handled = false; + + // Track EGL import failures to avoid repeated attempts + // Static because if EGL fails once, it will likely keep failing (e.g., no GPU) + static bool egl_import_failed_permanently = false; + + { + std::lock_guard lock(wpe_buffer_mutex_); + + // Store reference to previous buffer - we'll release it AFTER importing the new one + // This ensures the EGL image's backing memory stays valid until we have a new frame + previous_buffer = current_buffer_; + + // Destroy previous EGL image if we created one + if (current_egl_image_ != nullptr && egl_display_ != nullptr) { + static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; + if (eglDestroyImageKHR == nullptr) { + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + } + if (eglDestroyImageKHR != nullptr) { + eglDestroyImageKHR(static_cast(egl_display_), + static_cast(current_egl_image_)); + } + current_egl_image_ = nullptr; + } + + // Check buffer type to determine best rendering path + bool is_dma_buf = WPE_IS_BUFFER_DMA_BUF(buffer); + bool is_shm = WPE_IS_BUFFER_SHM(buffer); + + // === Priority 1: Try EGL image import (zero-copy, best performance) === + // Only attempt EGL for DMA-BUF buffers (SHM buffers cannot be imported via EGL) + // Skip if previous EGL attempts failed + if (egl_display_ != nullptr && + is_dma_buf && !egl_import_failed_permanently) { + GError* error = nullptr; + void* egl_image = wpe_buffer_import_to_egl_image(buffer, &error); + + if (egl_image != nullptr) { + current_egl_image_ = egl_image; + current_buffer_width_ = buf_width; + current_buffer_height_ = buf_height; + buffer_handled = true; + } else { + // Mark EGL as permanently failed so we don't keep trying + // This is common in VMs or software-only environments + egl_import_failed_permanently = true; + if (error != nullptr) { + g_clear_error(&error); + } + } + } + + // === Priority 2: Direct SHM buffer access (no GBM required) === + // WPEBufferSHM provides direct pixel access without requiring GBM device + if (!buffer_handled && is_shm) { + WPEBufferSHM* shm_buffer = WPE_BUFFER_SHM(buffer); + GBytes* data = wpe_buffer_shm_get_data(shm_buffer); + + if (data != nullptr) { + guint stride = wpe_buffer_shm_get_stride(shm_buffer); + WPEPixelFormat format = wpe_buffer_shm_get_format(shm_buffer); + + gsize size; + const uint8_t* pixels = static_cast(g_bytes_get_data(data, &size)); + + if (pixels != nullptr && size > 0) { + // Store in pixel buffer for software rendering + size_t write_idx = write_buffer_index_.load(std::memory_order_relaxed); + auto& pixel_buffer = pixel_buffers_[write_idx]; + + if (pixel_buffer.data.size() != size) { + pixel_buffer.data.resize(size); + } + memcpy(pixel_buffer.data.data(), pixels, size); + + // WPE SHM buffers use ARGB8888 format (BGRA in memory on little-endian) + // Flutter expects RGBA8888, so we need to convert + // ConvertARGB32ToRGBA handles the BGRA -> RGBA conversion + if (format == WPE_PIXEL_FORMAT_ARGB8888) { + ConvertARGB32ToRGBA(pixel_buffer.data.data(), // source (in-place) + pixel_buffer.data.data(), // destination (in-place) + buf_width, buf_height, + stride); + } + + pixel_buffer.width = buf_width; + pixel_buffer.height = buf_height; + + // Swap buffers + { + std::lock_guard swap_lock(buffer_swap_mutex_); + read_buffer_index_.store(write_idx, std::memory_order_release); + write_buffer_index_.store((write_idx + 1) % kNumBuffers, std::memory_order_relaxed); + } + + current_buffer_width_ = buf_width; + current_buffer_height_ = buf_height; + buffer_handled = true; + } + // Note: Don't unref data - it's borrowed from the buffer + } + } + + // === Priority 3: Generic pixel import (works for DMA-BUF with GBM device) === + // This is a fallback for DMA-BUF when EGL failed but GBM device is available + if (!buffer_handled) { + GError* error = nullptr; + GBytes* pixels = wpe_buffer_import_to_pixels(buffer, &error); + if (pixels != nullptr) { + gsize size; + const uint8_t* data = static_cast(g_bytes_get_data(pixels, &size)); + + // Store in pixel buffer for software rendering + size_t write_idx = write_buffer_index_.load(std::memory_order_relaxed); + auto& pixel_buffer = pixel_buffers_[write_idx]; + + if (pixel_buffer.data.size() != size) { + pixel_buffer.data.resize(size); + } + memcpy(pixel_buffer.data.data(), data, size); + + // GBM pixel import also returns ARGB8888, convert to RGBA + uint32_t stride = buf_width * 4; + ConvertARGB32ToRGBA(pixel_buffer.data.data(), + pixel_buffer.data.data(), + buf_width, buf_height, + stride); + + pixel_buffer.width = buf_width; + pixel_buffer.height = buf_height; + + // Swap buffers + { + std::lock_guard swap_lock(buffer_swap_mutex_); + read_buffer_index_.store(write_idx, std::memory_order_release); + write_buffer_index_.store((write_idx + 1) % kNumBuffers, std::memory_order_relaxed); + } + + g_bytes_unref(pixels); + current_buffer_width_ = buf_width; + current_buffer_height_ = buf_height; + buffer_handled = true; + } else { + if (error != nullptr) { + g_clear_error(&error); + } + } + } + + if (!buffer_handled) { + debugLog("ERROR: No rendering method succeeded!"); + } + + // Store reference to current buffer - we keep it until the NEXT frame arrives + // This ensures the EGL image's backing DMA-BUF memory stays valid + current_buffer_ = buffer; + } + + // Release the PREVIOUS buffer now that we have a new one + // The previous EGL image has been destroyed and we have a new frame, + // so it's safe to let WPE reuse the old buffer's memory + if (previous_buffer != nullptr && wpe_view_ != nullptr && WPE_IS_BUFFER(previous_buffer)) { + wpe_view_buffer_released(wpe_view_, previous_buffer); + } + + if (buffer_handled && on_frame_available_) { + on_frame_available_(); + } +} +#endif + +void InAppWebView::ReadPixelsFromEglImage(void* egl_image, uint32_t width, uint32_t height) { + // CRITICAL: Check for GL context before any GL operations + // WPE WebKit calls this from its own thread which may not have a GL context + if (!HasCurrentGLContext()) { + return; + } + + EGLDisplay display = static_cast(egl_display_); + EGLImageKHR image = static_cast(egl_image); + + if (display == EGL_NO_DISPLAY || image == EGL_NO_IMAGE_KHR) { + return; + } + + // Create texture from EGL image if we haven't already + if (readback_texture_ == 0) { + glGenTextures(1, &readback_texture_); + } + + // Create FBO if needed + if (fbo_ == 0) { + glGenFramebuffers(1, &fbo_); + } + + glBindTexture(GL_TEXTURE_2D, readback_texture_); + + // Use the OES_EGL_image extension to create texture from EGL image + static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; + if (glEGLImageTargetTexture2DOES == nullptr) { + glEGLImageTargetTexture2DOES = + (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + } + + if (glEGLImageTargetTexture2DOES != nullptr) { + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + return; + } + + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, readback_texture_, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + return; + } + + size_t buffer_size = width * height * 4; // RGBA + + // Use triple buffering + size_t write_idx = write_buffer_index_.load(std::memory_order_relaxed); + auto& buffer = pixel_buffers_[write_idx]; + + if (buffer.data.size() != buffer_size) { + buffer.data.resize(buffer_size); + } + + buffer.width = width; + buffer.height = height; + + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data.data()); + + { + std::lock_guard lock(buffer_swap_mutex_); + read_buffer_index_.store(write_idx, std::memory_order_release); + write_buffer_index_.store((write_idx + 1) % kNumBuffers, std::memory_order_relaxed); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); +} + +// === Navigation Methods === + +void InAppWebView::loadUrl(const std::string& url) { + if (webview_ == nullptr) + return; + + webkit_web_view_load_uri(webview_, url.c_str()); +} + +void InAppWebView::loadUrl(const std::shared_ptr& urlRequest) { + if (webview_ == nullptr || !urlRequest) + return; + + if (!urlRequest->url.has_value()) { + return; + } + + std::string method = urlRequest->method.value_or("GET"); + + if (method == "GET" && !urlRequest->body.has_value() && !urlRequest->headers.has_value()) { + webkit_web_view_load_uri(webview_, urlRequest->url.value().c_str()); + return; + } + + // Create a WebKitURIRequest for more complex requests + WebKitURIRequest* request = webkit_uri_request_new(urlRequest->url.value().c_str()); + + if (urlRequest->headers.has_value()) { + SoupMessageHeaders* headers = webkit_uri_request_get_http_headers(request); + for (const auto& header : urlRequest->headers.value()) { + soup_message_headers_append(headers, header.first.c_str(), header.second.c_str()); + } + } + + webkit_web_view_load_request(webview_, request); + g_object_unref(request); +} + +void InAppWebView::loadData(const std::string& data, const std::string& mime_type, + const std::string& encoding, const std::string& base_url) { + if (webview_ == nullptr) + return; + + g_message("InAppWebView: loadData() called while loading, stopping current load first"); + webkit_web_view_stop_loading(webview_); + // Give WebKit/WPE FDO a moment to clean up pending operations + while (g_main_context_iteration(NULL, FALSE)) { } + + GBytes* bytes = g_bytes_new(data.data(), data.size()); + webkit_web_view_load_bytes(webview_, bytes, mime_type.c_str(), encoding.c_str(), + base_url.c_str()); + g_bytes_unref(bytes); +} + +void InAppWebView::loadFile(const std::string& asset_file_path) { + if (webview_ == nullptr) + return; + + g_message("InAppWebView: loadFile() called while loading, stopping current load first"); + webkit_web_view_stop_loading(webview_); + // Give WebKit/WPE FDO a moment to clean up pending operations + while (g_main_context_iteration(NULL, FALSE)) { } + + // Get the path to the running executable + char exe_path[PATH_MAX]; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len == -1) { + debugLog("Failed to get executable path for loadFile"); + return; + } + exe_path[len] = '\0'; + + // Build the absolute path to the Flutter asset + std::filesystem::path exe_dir = std::filesystem::path(exe_path).parent_path(); + std::filesystem::path flutter_asset_path = + exe_dir / "data" / "flutter_assets" / asset_file_path; + + if (!std::filesystem::exists(flutter_asset_path)) { + debugLog("Asset file not found: " + flutter_asset_path.string()); + return; + } + + std::string file_url = "file://" + flutter_asset_path.string(); + webkit_web_view_load_uri(webview_, file_url.c_str()); +} + +void InAppWebView::postUrl(const std::string& url, const std::vector& postData) { + if (webview_ == nullptr) + return; + + g_message("InAppWebView: postUrl() called while loading, stopping current load first"); + webkit_web_view_stop_loading(webview_); + // Give WebKit/WPE FDO a moment to clean up pending operations + while (g_main_context_iteration(NULL, FALSE)) { } + + // WPE WebKit's webkit_web_view_load_request() doesn't support POST body directly. + // We use JavaScript XMLHttpRequest to perform the POST and load the result. + // This approach is similar to the Web platform implementation. + + // Convert post data to base64 for safe embedding in JavaScript + gchar* base64_data = g_base64_encode(postData.data(), postData.size()); + + // Escape URL for JavaScript string + std::string escaped_url = url; + replace_all(escaped_url, "\\", "\\\\"); + replace_all(escaped_url, "'", "\\'"); + replace_all(escaped_url, "\n", "\\n"); + replace_all(escaped_url, "\r", "\\r"); + + // Create JavaScript that performs the POST request and loads the result + std::string js = R"( +(function() { + var xhr = new XMLHttpRequest(); + xhr.open('POST', ')" + escaped_url + R"(', true); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.onload = function() { + if (xhr.status >= 200 && xhr.status < 300) { + document.open(); + document.write(xhr.responseText); + document.close(); + } + }; + xhr.onerror = function() { + console.error('postUrl XHR failed for: )" + escaped_url + R"('); + }; + var postData = atob(')" + std::string(base64_data) + R"('); + xhr.send(postData); +})(); +)"; + + g_free(base64_data); + + // First load about:blank to ensure we have a document context, then execute the XHR + // We need to inject the script after a page load + webkit_web_view_load_html(webview_, + "", + url.c_str()); + + // Use evaluateJavascript to run the XHR after the blank page loads + // We need to wait for the load to complete, so we use a delayed approach + std::string* js_copy = new std::string(js); + g_timeout_add(100, [](gpointer user_data) -> gboolean { + auto* data = static_cast*>(user_data); + if (data->first->webview() != nullptr) { + webkit_web_view_evaluate_javascript( + data->first->webview(), + data->second->c_str(), + -1, + nullptr, // world + nullptr, // source_uri + nullptr, // cancellable + nullptr, // callback + nullptr); // user_data + } + delete data->second; + delete data; + return G_SOURCE_REMOVE; + }, new std::pair(this, js_copy)); +} + +void InAppWebView::reload() { + if (webview_ == nullptr) + return; + + webkit_web_view_reload(webview_); +} + +void InAppWebView::reloadFromOrigin() { + if (webview_ == nullptr) + return; + + g_message("InAppWebView: reloadFromOrigin() called while loading, stopping current load first"); + webkit_web_view_stop_loading(webview_); + // Give WebKit/WPE FDO a moment to clean up pending operations + while (g_main_context_iteration(NULL, FALSE)) { } + + webkit_web_view_reload_bypass_cache(webview_); +} + +void InAppWebView::goBack() { + if (webview_ == nullptr) + return; + + webkit_web_view_go_back(webview_); +} + +void InAppWebView::goForward() { + if (webview_ == nullptr) + return; + + webkit_web_view_go_forward(webview_); +} + +bool InAppWebView::canGoBack() const { + if (webview_ == nullptr) + return false; + return webkit_web_view_can_go_back(webview_); +} + +bool InAppWebView::canGoForward() const { + if (webview_ == nullptr) + return false; + return webkit_web_view_can_go_forward(webview_); +} + +void InAppWebView::stopLoading() { + if (webview_ == nullptr) + return; + webkit_web_view_stop_loading(webview_); +} + +bool InAppWebView::isLoading() const { + if (webview_ == nullptr) + return false; + return webkit_web_view_is_loading(webview_); +} + +// === Navigation History === + +FlValue* InAppWebView::getCopyBackForwardList() const { + if (webview_ == nullptr) { + return fl_value_new_null(); + } + + WebKitBackForwardList* bfList = webkit_web_view_get_back_forward_list(webview_); + if (bfList == nullptr) { + return fl_value_new_null(); + } + + GList* backList = webkit_back_forward_list_get_back_list(bfList); + WebKitBackForwardListItem* currentItem = webkit_back_forward_list_get_current_item(bfList); + GList* forwardList = webkit_back_forward_list_get_forward_list(bfList); + + int currentIndex = g_list_length(backList); + + FlValue* historyList = fl_value_new_list(); + int index = 0; + + for (GList* l = backList; l != nullptr; l = l->next) { + WebKitBackForwardListItem* item = WEBKIT_BACK_FORWARD_LIST_ITEM(l->data); + + const gchar* originalUri = webkit_back_forward_list_item_get_original_uri(item); + const gchar* title = webkit_back_forward_list_item_get_title(item); + const gchar* uri = webkit_back_forward_list_item_get_uri(item); + + FlValue* itemMap = to_fl_map({ + {"originalUrl", make_fl_value(originalUri ? originalUri : "")}, + {"title", make_fl_value(title ? title : "")}, + {"url", make_fl_value(uri ? uri : "")}, + {"index", make_fl_value(index)}, + {"offset", make_fl_value(index - currentIndex)}, + }); + + fl_value_append_take(historyList, itemMap); + index++; + } + + if (currentItem != nullptr) { + const gchar* originalUri = webkit_back_forward_list_item_get_original_uri(currentItem); + const gchar* title = webkit_back_forward_list_item_get_title(currentItem); + const gchar* uri = webkit_back_forward_list_item_get_uri(currentItem); + + FlValue* itemMap = to_fl_map({ + {"originalUrl", make_fl_value(originalUri ? originalUri : "")}, + {"title", make_fl_value(title ? title : "")}, + {"url", make_fl_value(uri ? uri : "")}, + {"index", make_fl_value(index)}, + {"offset", make_fl_value(index - currentIndex)}, + }); + + fl_value_append_take(historyList, itemMap); + index++; + } + + for (GList* l = forwardList; l != nullptr; l = l->next) { + WebKitBackForwardListItem* item = WEBKIT_BACK_FORWARD_LIST_ITEM(l->data); + + const gchar* originalUri = webkit_back_forward_list_item_get_original_uri(item); + const gchar* title = webkit_back_forward_list_item_get_title(item); + const gchar* uri = webkit_back_forward_list_item_get_uri(item); + + FlValue* itemMap = to_fl_map({ + {"originalUrl", make_fl_value(originalUri ? originalUri : "")}, + {"title", make_fl_value(title ? title : "")}, + {"url", make_fl_value(uri ? uri : "")}, + {"index", make_fl_value(index)}, + {"offset", make_fl_value(index - currentIndex)}, + }); + + fl_value_append_take(historyList, itemMap); + index++; + } + + return to_fl_map({ + {"list", historyList}, + {"currentIndex", make_fl_value(currentIndex)}, + }); +} + +void InAppWebView::goBackOrForward(int steps) { + if (webview_ == nullptr) + return; + + WebKitBackForwardList* bfList = webkit_web_view_get_back_forward_list(webview_); + if (bfList == nullptr) + return; + + WebKitBackForwardListItem* item = webkit_back_forward_list_get_nth_item(bfList, steps); + if (item != nullptr) { + webkit_web_view_go_to_back_forward_list_item(webview_, item); + } +} + +bool InAppWebView::canGoBackOrForward(int steps) const { + if (webview_ == nullptr) + return false; + + WebKitBackForwardList* bfList = webkit_web_view_get_back_forward_list(webview_); + if (bfList == nullptr) + return false; + + WebKitBackForwardListItem* item = webkit_back_forward_list_get_nth_item(bfList, steps); + return item != nullptr; +} + +// === Getters === + +std::optional InAppWebView::getUrl() const { + if (webview_ == nullptr) + return std::nullopt; + const gchar* uri = webkit_web_view_get_uri(webview_); + if (uri == nullptr) + return std::nullopt; + return std::string(uri); +} + +std::optional InAppWebView::getTitle() const { + if (webview_ == nullptr) + return std::nullopt; + const gchar* title = webkit_web_view_get_title(webview_); + if (title == nullptr) + return std::nullopt; + return std::string(title); +} + +int64_t InAppWebView::getProgress() const { + if (webview_ == nullptr) + return 0; + return static_cast(webkit_web_view_get_estimated_load_progress(webview_) * 100); +} + +// === TLS/SSL Certificate === + +std::optional InAppWebView::getCertificate() const { + if (webview_ == nullptr) { + return std::nullopt; + } + + GTlsCertificate* certificate = nullptr; + GTlsCertificateFlags errors = static_cast(0); + + if (!webkit_web_view_get_tls_info(webview_, &certificate, &errors)) { + return std::nullopt; + } + + if (certificate == nullptr) { + return std::nullopt; + } + + GByteArray* der_data = nullptr; + g_object_get(certificate, "certificate", &der_data, nullptr); + + if (der_data == nullptr || der_data->len == 0) { + if (der_data != nullptr) { + g_byte_array_unref(der_data); + } + return std::nullopt; + } + + std::vector certData(der_data->data, der_data->data + der_data->len); + g_byte_array_unref(der_data); + + return SslCertificate(certData); +} + +HitTestResult InAppWebView::getHitTestResult() const { + if (last_hit_test_result_ == nullptr) { + return HitTestResult(HitTestResultType::UNKNOWN_TYPE); + } + return HitTestResult::fromWebKitHitTestResult(last_hit_test_result_); +} + +// === JavaScript === + +void InAppWebView::evaluateJavascript( + const std::string& source, + const std::optional& worldName, + std::function&)> callback) { + if (webview_ == nullptr) { + if (callback) + callback(std::nullopt); + return; + } + + struct CallbackData { + std::function&)> callback; + }; + + auto* cb_data = new CallbackData{std::move(callback)}; + + const char* world = worldName.has_value() ? worldName->c_str() : nullptr; + + webkit_web_view_evaluate_javascript( + webview_, source.c_str(), source.length(), + world, // world name (for content world support) + nullptr, // source URI + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* data = static_cast(user_data); + GError* error = nullptr; + JSCValue* js_result = + webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(source), result, &error); + + if (error != nullptr) { + if (data->callback) + data->callback(std::nullopt); + g_error_free(error); + } else if (js_result != nullptr) { + // Use JSON.stringify on the result via JSC to get proper JSON + JSCContext* context = jsc_value_get_context(js_result); + g_autoptr(JSCValue) json_stringify = jsc_context_evaluate( + context, + "(function(v) { return JSON.stringify(v); })", + -1); + g_autoptr(JSCValue) json_value = jsc_value_function_call( + json_stringify, JSC_TYPE_VALUE, js_result, G_TYPE_NONE); + + if (json_value != nullptr && jsc_value_is_string(json_value)) { + g_autofree gchar* str = jsc_value_to_string(json_value); + if (data->callback) { + if (str) { + data->callback(std::string(str)); + } else { + data->callback(std::nullopt); + } + } + } else { + if (data->callback) + data->callback(std::nullopt); + } + g_object_unref(js_result); + } else { + if (data->callback) + data->callback(std::nullopt); + } + + delete data; + }, + cb_data); +} + +// Helper function to convert FlValue to GVariant +void InAppWebView::callAsyncJavaScript( + const std::string& functionBody, + const std::string& argumentsJson, + const std::vector& argumentKeys, + const std::optional& worldName, + std::function callback) { + if (webview_ == nullptr) { + if (callback) { + callback(R"({"value":null,"error":"WebView not available"})"); + } + return; + } + + // Build the wrapped function body that: + // 1. Parses the JSON-encoded arguments + // 2. Destructures them into local variables + // 3. Executes the user's function body + std::string wrappedBody; + + if (!argumentKeys.empty()) { + // Build destructuring: const {key1, key2, ...} = JSON.parse(__args__); + wrappedBody = "const {"; + for (size_t i = 0; i < argumentKeys.size(); i++) { + if (i > 0) wrappedBody += ", "; + wrappedBody += argumentKeys[i]; + } + wrappedBody += "} = JSON.parse(__args__);\n"; + } + wrappedBody += functionBody; + + // Pass the JSON string as a single __args__ argument + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&builder, "{sv}", "__args__", g_variant_new_string(argumentsJson.c_str())); + GVariant* gvariant_args = g_variant_builder_end(&builder); + + const char* world = worldName.has_value() ? worldName->c_str() : nullptr; + + struct CallbackData { + std::function callback; + }; + auto* cb_data = new CallbackData{std::move(callback)}; + + webkit_web_view_call_async_javascript_function( + webview_, wrappedBody.c_str(), -1, // length: null-terminated + gvariant_args, world, + nullptr, // source_uri + nullptr, // cancellable + [](GObject* source, GAsyncResult* result, gpointer user_data) { + auto* data = static_cast(user_data); + GError* error = nullptr; + JSCValue* js_result = webkit_web_view_call_async_javascript_function_finish( + WEBKIT_WEB_VIEW(source), result, &error); + + std::string json_result; + + if (error != nullptr) { + // Escape the error message for JSON + std::string error_msg = error->message ? error->message : "Unknown error"; + // Simple JSON escaping for the error message + std::string escaped_error; + for (char c : error_msg) { + switch (c) { + case '"': escaped_error += "\\\""; break; + case '\\': escaped_error += "\\\\"; break; + case '\n': escaped_error += "\\n"; break; + case '\r': escaped_error += "\\r"; break; + case '\t': escaped_error += "\\t"; break; + default: escaped_error += c; break; + } + } + json_result = "{\"value\":null,\"error\":\"" + escaped_error + "\"}"; + g_error_free(error); + } else if (js_result != nullptr) { + // Use JSON.stringify on the result via JSC to get proper JSON + JSCContext* context = jsc_value_get_context(js_result); + g_autoptr(JSCValue) json_stringify = jsc_context_evaluate( + context, + "(function(v) { return JSON.stringify({value: v, error: null}); })", + -1); + g_autoptr(JSCValue) json_value = jsc_value_function_call( + json_stringify, JSC_TYPE_VALUE, js_result, G_TYPE_NONE); + + if (json_value != nullptr && jsc_value_is_string(json_value)) { + g_autofree gchar* str = jsc_value_to_string(json_value); + json_result = str ? str : "{\"value\":null,\"error\":null}"; + } else { + json_result = "{\"value\":null,\"error\":null}"; + } + g_object_unref(js_result); + } else { + json_result = "{\"value\":null,\"error\":null}"; + } + + if (data->callback) { + data->callback(json_result); + } + delete data; + }, + cb_data); +} + +void InAppWebView::injectJavascriptFileFromUrl(const std::string& urlFile) { + std::string script = + "(function() {" + " var script = document.createElement('script');" + " script.src = '" + + urlFile + + "';" + " document.head.appendChild(script);" + "})();"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::injectCSSCode(const std::string& source) { + // Escape single quotes and newlines in CSS + std::string escaped_source = source; + size_t pos = 0; + while ((pos = escaped_source.find("'", pos)) != std::string::npos) { + escaped_source.replace(pos, 1, "\\'"); + pos += 2; + } + pos = 0; + while ((pos = escaped_source.find("\n", pos)) != std::string::npos) { + escaped_source.replace(pos, 1, "\\n"); + pos += 2; + } + + std::string script = + "(function() {" + " var style = document.createElement('style');" + " style.textContent = '" + + escaped_source + + "';" + " document.head.appendChild(style);" + "})();"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::injectCSSFileFromUrl(const std::string& urlFile) { + std::string script = + "(function() {" + " var link = document.createElement('link');" + " link.rel = 'stylesheet';" + " link.href = '" + + urlFile + + "';" + " document.head.appendChild(link);" + "})();"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +// === User Scripts === + +void InAppWebView::addUserScript(std::shared_ptr userScript) { + if (user_content_controller_) { + user_content_controller_->addUserScript(userScript); + } +} + +void InAppWebView::removeUserScriptAt(size_t index, UserScriptInjectionTime injectionTime) { + if (user_content_controller_) { + user_content_controller_->removeUserScriptAt(index, injectionTime); + } +} + +void InAppWebView::removeUserScriptsByGroupName(const std::string& groupName) { + if (user_content_controller_) { + user_content_controller_->removeUserScriptsByGroupName(groupName); + } +} + +void InAppWebView::removeAllUserScripts() { + if (user_content_controller_) { + user_content_controller_->removeAllUserScripts(); + } +} + +// === Web Message Listener === + +void InAppWebView::addWebMessageListener(const std::string& jsObjectName, + const std::vector& allowedOriginRules) { + if (webview_ == nullptr || jsObjectName.empty() || messenger_ == nullptr) { + return; + } + + // Generate a unique ID for this listener + static int64_t listener_counter = 0; + std::string listenerId = std::to_string(g_get_monotonic_time()) + "_" + + std::to_string(++listener_counter); + + // Create the native WebMessageListener with its dedicated channel + // This follows the federated plugin pattern + std::set originRulesSet(allowedOriginRules.begin(), allowedOriginRules.end()); + auto listener = std::make_unique( + messenger_, listenerId, jsObjectName, originRulesSet, this); + + // Store the listener by jsObjectName (so we can find it when JS posts a message) + web_message_listeners_[jsObjectName] = std::move(listener); + + // Build the allowed origin rules JSON array for JavaScript injection + std::string allowedOriginRulesJs = "["; + for (size_t i = 0; i < allowedOriginRules.size(); ++i) { + const std::string& rule = allowedOriginRules[i]; + if (rule == "*") { + allowedOriginRulesJs += "'*'"; + } else { + // Parse the rule to extract scheme, host, port + // Format: scheme://host[:port] + std::string scheme, host; + int port = 0; + + size_t schemeEnd = rule.find("://"); + if (schemeEnd != std::string::npos) { + scheme = rule.substr(0, schemeEnd); + std::string rest = rule.substr(schemeEnd + 3); + + size_t portStart = rest.find(':'); + if (portStart != std::string::npos) { + host = rest.substr(0, portStart); + try { + port = std::stoi(rest.substr(portStart + 1)); + } catch (...) { + port = 0; + } + } else { + host = rest; + } + } + + // Escape single quotes in host + std::string hostEscaped = host; + size_t pos = 0; + while ((pos = hostEscaped.find("'", pos)) != std::string::npos) { + hostEscaped.replace(pos, 1, "\\'"); + pos += 2; + } + + allowedOriginRulesJs += "{scheme: '" + scheme + "', host: "; + if (host.empty()) { + allowedOriginRulesJs += "null"; + } else { + allowedOriginRulesJs += "'" + hostEscaped + "'"; + } + allowedOriginRulesJs += ", port: "; + if (port == 0) { + allowedOriginRulesJs += "null"; + } else { + allowedOriginRulesJs += std::to_string(port); + } + allowedOriginRulesJs += "}"; + } + if (i < allowedOriginRules.size() - 1) { + allowedOriginRulesJs += ", "; + } + } + allowedOriginRulesJs += "]"; + + // Create the JavaScript to inject + std::string jsSource = WebMessageListenerJS::createWebMessageListenerInjectionJs( + jsObjectName, allowedOriginRulesJs); + + // Create a user script for this web message listener + // We use a unique group name to allow removal if needed + std::string groupName = "WebMessageListener-" + jsObjectName; + + auto userScript = std::make_shared( + groupName, + jsSource, + UserScriptInjectionTime::atDocumentStart, + true, // forMainFrameOnly + std::nullopt // allowedOriginRules (already handled in JS) + ); + + // Add the script to the user content controller + if (user_content_controller_) { + user_content_controller_->addUserScript(userScript); + } +} + +// === Web Message Channel === + +void InAppWebView::createWebMessageChannel( + std::function&)> callback) { + if (webview_ == nullptr || messenger_ == nullptr) { + if (callback) callback(std::nullopt); + return; + } + + // Generate a unique channel ID using timestamp and random number + static int64_t channel_counter = 0; + std::string channelId = std::to_string(g_get_monotonic_time()) + "_" + + std::to_string(++channel_counter); + + // Create the JavaScript to create the MessageChannel + std::string js = WebMessageChannelJS::createWebMessageChannelJs(channelId); + + // Capture variables for the callback + InAppWebView* self = this; + FlBinaryMessenger* messenger = messenger_; + + // Execute JavaScript to create the channel + evaluateJavascript(js, std::nullopt, [self, callback, channelId, messenger](const std::optional& result) { + // If we got a result, the channel was created successfully + if (result.has_value()) { + // Create and store the WebMessageChannel object + auto channel = std::make_unique(messenger, channelId, self); + self->web_message_channels_[channelId] = std::move(channel); + + callback(channelId); + } else { + callback(std::nullopt); + } + }); +} + +WebMessageChannel* InAppWebView::getWebMessageChannel(const std::string& channelId) const { + auto it = web_message_channels_.find(channelId); + if (it != web_message_channels_.end()) { + return it->second.get(); + } + return nullptr; +} + +void InAppWebView::postWebMessage(const std::string& messageData, + const std::string& targetOrigin, + int64_t messageType) { + if (webview_ == nullptr) return; + + // Convert message data to JavaScript expression + std::string messageDataJs; + if (messageType == 1) { + // ArrayBuffer - messageData contains comma-separated byte values + messageDataJs = "new Uint8Array([" + messageData + "]).buffer"; + } else { + // String - escape for JavaScript + std::string escaped; + escaped.reserve(messageData.size() * 2); + for (char c : messageData) { + switch (c) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + default: escaped += c; break; + } + } + messageDataJs = "\"" + escaped + "\""; + } + + // Post message to window (no ports for now - ports are handled via channel) + std::string js = WebMessageChannelJS::postWebMessageJs(messageDataJs, targetOrigin, ""); + evaluateJavascript(js, std::nullopt, nullptr); +} + +void InAppWebView::setWebMessageCallback(const std::string& channelId, int portIndex) { + if (webview_ == nullptr || channelId.empty()) return; + + std::string js = WebMessageChannelJS::setWebMessageCallbackJs(channelId, portIndex); + evaluateJavascript(js, std::nullopt, nullptr); +} + +void InAppWebView::postWebMessageOnPort(const std::string& channelId, int portIndex, + const std::string& messageData, int64_t messageType) { + if (webview_ == nullptr || channelId.empty()) return; + + // Convert message data to JavaScript expression + std::string messageDataJs; + if (messageType == 1) { + // ArrayBuffer - messageData contains comma-separated byte values + messageDataJs = "new Uint8Array([" + messageData + "]).buffer"; + } else { + // String - escape for JavaScript + std::string escaped; + escaped.reserve(messageData.size() * 2); + for (char c : messageData) { + switch (c) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + default: escaped += c; break; + } + } + messageDataJs = "\"" + escaped + "\""; + } + + std::string js = WebMessageChannelJS::postMessageJs(channelId, portIndex, messageDataJs); + evaluateJavascript(js, std::nullopt, nullptr); +} + +void InAppWebView::closeWebMessagePort(const std::string& channelId, int portIndex) { + if (webview_ == nullptr || channelId.empty()) return; + + std::string js = WebMessageChannelJS::closePortJs(channelId, portIndex); + evaluateJavascript(js, std::nullopt, nullptr); +} + +void InAppWebView::disposeWebMessageChannel(const std::string& channelId) { + if (channelId.empty()) return; + + // Execute JavaScript to clean up the channel + if (webview_ != nullptr) { + std::string js = WebMessageChannelJS::disposeChannelJs(channelId); + evaluateJavascript(js, std::nullopt, nullptr); + } + + // Remove the channel from our map + web_message_channels_.erase(channelId); +} + +// === HTML Content === + +void InAppWebView::getHtml(std::function&)> callback) { + evaluateJavascript("document.documentElement.outerHTML", std::nullopt, callback); +} + +// === Screenshot === + +void InAppWebView::takeScreenshot(std::function>&)> callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) { + callback(std::nullopt); + } + return; + } + + // Get the current pixel buffer dimensions + uint32_t width = 0; + uint32_t height = 0; + size_t buffer_size = GetPixelBufferSize(&width, &height); + + if (buffer_size == 0 || width == 0 || height == 0) { + callback(std::nullopt); + return; + } + + // Allocate a temporary buffer for the pixel data + std::vector pixel_data(buffer_size); + + if (!CopyPixelBufferTo(pixel_data.data(), buffer_size, &width, &height)) { + callback(std::nullopt); + return; + } + + // Create a Cairo surface from the RGBA pixel data + // Note: WPE provides RGBA data, but Cairo uses ARGB (pre-multiplied alpha in native byte order) + // We need to convert RGBA -> ARGB32 format + + // Allocate buffer for Cairo ARGB32 format (same size) + std::vector argb_data(width * height * 4); + + // Convert RGBA -> ARGB32 (Cairo's native format) + // Cairo ARGB32 format on little-endian: BGRA in memory + for (uint32_t i = 0; i < width * height; ++i) { + uint8_t r = pixel_data[i * 4 + 0]; + uint8_t g = pixel_data[i * 4 + 1]; + uint8_t b = pixel_data[i * 4 + 2]; + uint8_t a = pixel_data[i * 4 + 3]; + + // Cairo ARGB32 on little-endian = BGRA in memory + argb_data[i * 4 + 0] = b; + argb_data[i * 4 + 1] = g; + argb_data[i * 4 + 2] = r; + argb_data[i * 4 + 3] = a; + } + + // Create Cairo surface from the ARGB data + cairo_surface_t* surface = cairo_image_surface_create_for_data( + argb_data.data(), + CAIRO_FORMAT_ARGB32, + static_cast(width), + static_cast(height), + static_cast(width * 4) // stride + ); + + if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surface); + callback(std::nullopt); + return; + } + + // Write PNG to a memory buffer using cairo_surface_write_to_png_stream + std::vector png_data; + + cairo_status_t status = cairo_surface_write_to_png_stream( + surface, + [](void* closure, const unsigned char* data, unsigned int length) -> cairo_status_t { + auto* output = static_cast*>(closure); + output->insert(output->end(), data, data + length); + return CAIRO_STATUS_SUCCESS; + }, + &png_data + ); + + cairo_surface_destroy(surface); + + if (status != CAIRO_STATUS_SUCCESS || png_data.empty()) { + callback(std::nullopt); + return; + } + + callback(png_data); +} + +// === Session State === + +std::optional> InAppWebView::saveState() const { + if (webview_ == nullptr) { + return std::nullopt; + } + + // Get the current session state from WPE WebKit + WebKitWebViewSessionState* session_state = webkit_web_view_get_session_state(webview_); + if (session_state == nullptr) { + return std::nullopt; + } + + // Serialize the session state to GBytes + GBytes* bytes = webkit_web_view_session_state_serialize(session_state); + webkit_web_view_session_state_unref(session_state); + + if (bytes == nullptr) { + return std::nullopt; + } + + // Copy the data to a vector + gsize size = 0; + gconstpointer data = g_bytes_get_data(bytes, &size); + + if (data == nullptr || size == 0) { + g_bytes_unref(bytes); + return std::nullopt; + } + + std::vector result(static_cast(data), + static_cast(data) + size); + + g_bytes_unref(bytes); + return result; +} + +bool InAppWebView::restoreState(const std::vector& stateData) { + if (webview_ == nullptr || stateData.empty()) { + return false; + } + + // Create GBytes from the state data + GBytes* bytes = g_bytes_new(stateData.data(), stateData.size()); + if (bytes == nullptr) { + return false; + } + + // Create session state from the serialized data + WebKitWebViewSessionState* session_state = webkit_web_view_session_state_new(bytes); + g_bytes_unref(bytes); + + if (session_state == nullptr) { + return false; + } + + // Restore the session state + webkit_web_view_restore_session_state(webview_, session_state); + webkit_web_view_session_state_unref(session_state); + + return true; +} + +// === Zoom === + +double InAppWebView::getZoomScale() const { + if (webview_ == nullptr) + return 1.0; + return webkit_web_view_get_zoom_level(webview_); +} + +void InAppWebView::setZoomScale(double zoomScale) { + if (webview_ == nullptr) + return; + webkit_web_view_set_zoom_level(webview_, zoomScale); +} + +// === Scroll === + +void InAppWebView::scrollTo(int64_t x, int64_t y, bool animated) { + std::string script = + animated ? "window.scrollTo({top: " + std::to_string(y) + ", left: " + std::to_string(x) + + ", behavior: 'smooth'});" + : "window.scrollTo(" + std::to_string(x) + ", " + std::to_string(y) + ");"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::scrollBy(int64_t x, int64_t y, bool animated) { + std::string script = + animated ? "window.scrollBy({top: " + std::to_string(y) + ", left: " + std::to_string(x) + + ", behavior: 'smooth'});" + : "window.scrollBy(" + std::to_string(x) + ", " + std::to_string(y) + ");"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::getScrollX(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(0); + return; + } + + evaluateJavascript( + "window.scrollX || window.pageXOffset || document.documentElement.scrollLeft || 0", + std::nullopt, + [callback](const std::optional& result) { + int64_t scrollX = 0; + if (result.has_value()) { + try { + scrollX = std::stoll(*result); + } catch (...) { + scrollX = 0; + } + } + callback(scrollX); + }); +} + +void InAppWebView::getScrollY(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(0); + return; + } + + evaluateJavascript( + "window.scrollY || window.pageYOffset || document.documentElement.scrollTop || 0", + std::nullopt, + [callback](const std::optional& result) { + int64_t scrollY = 0; + if (result.has_value()) { + try { + scrollY = std::stoll(*result); + } catch (...) { + scrollY = 0; + } + } + callback(scrollY); + }); +} + +void InAppWebView::canScrollVertically(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(false); + return; + } + + evaluateJavascript( + "document.documentElement.scrollHeight > document.documentElement.clientHeight", + std::nullopt, + [callback](const std::optional& result) { + bool canScroll = false; + if (result.has_value() && *result == "true") { + canScroll = true; + } + callback(canScroll); + }); +} + +void InAppWebView::canScrollHorizontally(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(false); + return; + } + + evaluateJavascript( + "document.documentElement.scrollWidth > document.documentElement.clientWidth", + std::nullopt, + [callback](const std::optional& result) { + bool canScroll = false; + if (result.has_value() && *result == "true") { + canScroll = true; + } + callback(canScroll); + }); +} + +// === Content Dimensions === + +void InAppWebView::getContentHeight(std::function callback) { + if (callback == nullptr) { + return; + } + + // Use JavaScript to get the document's scroll height + evaluateJavascript( + "Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)", + std::nullopt, + [callback](const std::optional& result) { + if (result.has_value()) { + try { + int64_t height = std::stoll(result.value()); + callback(height); + return; + } catch (...) { + // Fall through to default + } + } + callback(0); + }); +} + +void InAppWebView::getContentWidth(std::function callback) { + if (callback == nullptr) { + return; + } + + // Use JavaScript to get the document's scroll width + evaluateJavascript( + "Math.max(document.body.scrollWidth, document.documentElement.scrollWidth)", + std::nullopt, + [callback](const std::optional& result) { + if (result.has_value()) { + try { + int64_t width = std::stoll(result.value()); + callback(width); + return; + } catch (...) { + // Fall through to default + } + } + callback(0); + }); +} + +// === Settings === + +FlValue* InAppWebView::getSettings() const { + if (settings_) { + return settings_->toFlValue(); + } + return fl_value_new_null(); +} + +void InAppWebView::setSettings(const std::shared_ptr newSettings, + FlValue* newSettingsMap) { + if (newSettings && webview_) { + // Check if contentBlockers changed + FlValue* newContentBlockers = newSettings->contentBlockers; + + // Apply content blockers if they have been updated + if (content_blocker_handler_ != nullptr) { + // If new settings have contentBlockers, apply them + // This will replace any existing content blockers + content_blocker_handler_->setContentBlockers(newContentBlockers, nullptr); + } + + settings_ = newSettings; + settings_->applyToWebView(webview_); +#ifdef HAVE_WPE_PLATFORM + // Apply WPE Platform settings (dark mode, font settings, etc.) + if (wpe_display_ != nullptr) { + settings_->applyWpePlatformSettings(wpe_display_); + } +#endif + } +} + +// === Size Management === + +void InAppWebView::setSize(int width, int height) { + if (width == width_ && height == height_) + return; + + // Hide all popups on resize + HideAllPopups(); + + width_ = width; + height_ = height; + + // Resize the WPE backend +#ifdef HAVE_WPE_PLATFORM + if (wpe_toplevel_ != nullptr) { + wpe_toplevel_resize(wpe_toplevel_, width_, height_); + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_set_size(wpe_backend_, width_, height_); + } +#endif +} + +void InAppWebView::setScaleFactor(double scale_factor) { + if (scale_factor == scale_factor_) + return; + scale_factor_ = scale_factor; + + // WPE uses device scale factor +#ifdef HAVE_WPE_PLATFORM + // WPEPlatform: Notify the toplevel about scale changes + // This is needed for proper HiDPI rendering when scale changes dynamically + if (wpe_toplevel_ != nullptr) { + wpe_toplevel_scale_changed(wpe_toplevel_, scale_factor_); + } +#endif +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_set_device_scale_factor(wpe_backend_, scale_factor_); + } +#endif +} + +// === Activity State Management (like Cog browser) === + +void InAppWebView::setFocused(bool focused) { + if (focused == is_focused_) + return; + is_focused_ = focused; + + // Hide all popups when WebView loses focus + if (!focused) { + HideAllPopups(); + } + +#ifdef HAVE_WPE_PLATFORM + // WPEPlatform: Use wpe_view_focus_in/out API + if (wpe_view_ != nullptr) { + if (focused) { + wpe_view_focus_in(wpe_view_); + } else { + wpe_view_focus_out(wpe_view_); + } + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ != nullptr) { + if (focused) { + // Add focused state - also ensure visible and in_window are set + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_focused); + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_visible); + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_in_window); + } else { + // Remove only the focused state, keep visible and in_window so the webview + // continues to render and process basic events + wpe_view_backend_remove_activity_state(wpe_backend_, wpe_view_activity_state_focused); + } + } +#endif +} + +void InAppWebView::setVisible(bool visible) { + if (visible == is_visible_) + return; + is_visible_ = visible; + +#ifdef HAVE_WPE_PLATFORM + // WPEPlatform: Use wpe_view_set_visible and map/unmap + if (wpe_view_ != nullptr) { + wpe_view_set_visible(wpe_view_, visible); + if (visible) { + wpe_view_map(wpe_view_); + } else { + wpe_view_unmap(wpe_view_); + } + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ != nullptr) { + if (visible) { + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_visible); + wpe_view_backend_add_activity_state(wpe_backend_, wpe_view_activity_state_in_window); + } else { + wpe_view_backend_remove_activity_state(wpe_backend_, wpe_view_activity_state_visible); + wpe_view_backend_remove_activity_state(wpe_backend_, wpe_view_activity_state_in_window); + } + } +#endif +} + +uint32_t InAppWebView::getActivityState() const { +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + return wpe_view_backend_get_activity_state(wpe_backend_); + } +#endif + // WPEPlatform doesn't expose activity state in the same way + return 0; +} + +// === Refresh Rate Management === + +void InAppWebView::setTargetRefreshRate(uint32_t rate) { + target_refresh_rate_ = rate; +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + WPEScreen* screen = wpe_view_get_screen(wpe_view_); + if (screen != nullptr) { + wpe_screen_set_refresh_rate(screen, static_cast(rate)); + } + } +#endif +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + wpe_view_backend_set_target_refresh_rate(wpe_backend_, rate); + } +#endif +} + +uint32_t InAppWebView::getTargetRefreshRate() const { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + WPEScreen* screen = wpe_view_get_screen(wpe_view_); + if (screen != nullptr) { + int refreshRate = wpe_screen_get_refresh_rate(screen); + if (refreshRate > 0) { + return static_cast(refreshRate); + } + } + } +#endif +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + return wpe_view_backend_get_target_refresh_rate(wpe_backend_); + } +#endif + return target_refresh_rate_; +} + +// === Screen Scale Management === + +double InAppWebView::getScreenScale() const { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + WPEScreen* screen = wpe_view_get_screen(wpe_view_); + if (screen != nullptr) { + return wpe_screen_get_scale(screen); + } + } +#endif + // Legacy backend doesn't have screen scale API + return 1.0; +} + +void InAppWebView::setScreenScale(double scale) { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + WPEScreen* screen = wpe_view_get_screen(wpe_view_); + if (screen != nullptr) { + wpe_screen_set_scale(screen, scale); + } + } +#endif + // Legacy backend doesn't have screen scale API +} + +// === Visibility Management === + +bool InAppWebView::isVisible() const { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + return wpe_view_get_visible(wpe_view_); + } +#endif + // Legacy backend: return cached visibility state + return is_visible_; +} + +// === Fullscreen Control === + +void InAppWebView::requestEnterFullscreen() { +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_request_enter_fullscreen(wpe_backend_); + } +#endif + // Note: WPEPlatform uses WebKit enter-fullscreen/leave-fullscreen signals +} + +void InAppWebView::requestExitFullscreen() { +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_request_exit_fullscreen(wpe_backend_); + } +#endif + // Note: WPEPlatform uses WebKit enter-fullscreen/leave-fullscreen signals +} + +// === Pointer Lock Support (for games/immersive apps) === + +void InAppWebView::setPointerLockHandler(std::function handler) { + pointer_lock_handler_ = std::move(handler); + +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + wpe_view_backend_set_pointer_lock_handler( + wpe_backend_, + [](void* data, bool lock) -> bool { + auto* self = static_cast(data); + return self->OnPointerLockRequest(lock); + }, + this); + } +#endif + // Note: WPEPlatform uses wpe_view_lock_pointer/unlock_pointer APIs +} + +bool InAppWebView::OnPointerLockRequest(bool lock) { + if (pointer_lock_handler_) { + bool result = pointer_lock_handler_(lock); + if (result) { + pointer_locked_ = lock; + } + return result; + } + // Default: allow pointer lock + pointer_locked_ = lock; + return true; +} + +bool InAppWebView::requestPointerLock() { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + bool result = wpe_view_lock_pointer(wpe_view_); + if (result) { + pointer_locked_ = true; + } + return result; + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ != nullptr) { + bool result = wpe_view_backend_request_pointer_lock(wpe_backend_); + if (result) { + pointer_locked_ = true; + } + return result; + } +#endif + return false; +} + +bool InAppWebView::requestPointerUnlock() { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ != nullptr) { + bool result = wpe_view_unlock_pointer(wpe_view_); + if (result) { + pointer_locked_ = false; + } + return result; + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ != nullptr) { + bool result = wpe_view_backend_request_pointer_unlock(wpe_backend_); + if (result) { + pointer_locked_ = false; + } + return result; + } +#endif + return false; +} + +// === DOM Fullscreen Handler (like Cog browser on_dom_fullscreen_request) === + +bool InAppWebView::OnDomFullscreenRequest(bool fullscreen) { + // This is called when JavaScript requests fullscreen via Element.requestFullscreen() + // or exits via Document.exitFullscreen() + + if (waiting_fullscreen_notify_) { + // Already processing a fullscreen transition + return false; + } + + if (fullscreen == is_fullscreen_) { + // Already in the requested state - dispatch the event immediately + // This handles cases where DOM fullscreen requests are mixed with + // system fullscreen commands +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + if (is_fullscreen_) { + wpe_view_backend_dispatch_did_enter_fullscreen(wpe_backend_); + } else { + wpe_view_backend_dispatch_did_exit_fullscreen(wpe_backend_); + } + } +#endif + return true; + } + + waiting_fullscreen_notify_ = true; + is_fullscreen_ = fullscreen; + + // Notify the Dart side about the fullscreen request + // The Dart side should handle the actual fullscreen transition + if (channel_delegate_) { + if (fullscreen) { + channel_delegate_->onEnterFullscreen(); + } else { + channel_delegate_->onExitFullscreen(); + } + } + + // Dispatch the fullscreen state to WPE +#ifdef HAVE_WPE_BACKEND_LEGACY + if (wpe_backend_ != nullptr) { + if (fullscreen) { + wpe_view_backend_dispatch_did_enter_fullscreen(wpe_backend_); + } else { + wpe_view_backend_dispatch_did_exit_fullscreen(wpe_backend_); + } + } +#endif + + waiting_fullscreen_notify_ = false; + + return true; +} + +// === Input Handling === + +void InAppWebView::SetTextureOffset(double x, double y) { + texture_offset_x_ = x; + texture_offset_y_ = y; +} + +void InAppWebView::SetCursorPos(double x, double y) { + // Store logical coordinates + cursor_x_ = x; + cursor_y_ = y; + +#ifdef HAVE_WPE_PLATFORM + // Send pointer motion event using WPEPlatform API + if (wpe_view_ != nullptr) { + // Include button_state_ in modifiers so dragging (text selection) works correctly + WPEModifiers modifiers = static_cast(current_modifiers_ | button_state_); + WPEEvent* event = wpe_event_pointer_move_new( + WPE_EVENT_POINTER_MOVE, + wpe_view_, + WPE_INPUT_SOURCE_MOUSE, + static_cast(g_get_monotonic_time() / 1000), + modifiers, + x, // Scale to physical pixels + y, + 0.0, // delta_x (no delta for absolute position) + 0.0 // delta_y + ); + wpe_view_event(wpe_view_, event); + wpe_event_unref(event); + } +#elif defined(HAVE_WPE_BACKEND_LEGACY) + // Send pointer motion event with scaled coordinates (logical -> physical) + if (wpe_backend_ != nullptr) { + struct wpe_input_pointer_event event = {}; + event.type = wpe_input_pointer_event_type_motion; + event.time = g_get_monotonic_time() / 1000; // Convert to milliseconds + // Scale coordinates from logical to physical pixels + event.x = static_cast(x * scale_factor_); + event.y = static_cast(y * scale_factor_); + event.state = 0; // No button state change for motion events + event.modifiers = current_modifiers_ | button_state_; // Include pressed button modifiers + wpe_view_backend_dispatch_pointer_event(wpe_backend_, &event); + } +#endif +} + +void InAppWebView::SetPointerButton(int kind, int button, int clickCount) { + // Hide all popups on any button DOWN (kind=1 is Down per WpePointerEventKind enum) + if (kind == static_cast(WpePointerEventKind::Down)) { + HideAllPopups(); + } + +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ == nullptr) + return; + + // No need to scale coordinates from logical to physical pixels as WPEPlatform handles this internally. + double scaled_x = cursor_x_; + double scaled_y = cursor_y_; + + // Map button: Flutter uses 0=none, 1=primary, 2=secondary, 3=tertiary + // WPE/GDK uses: 1=Left, 2=Middle, 3=Right + guint wpe_button; + switch (button) { + case 1: + wpe_button = 1; + break; // Primary -> Left + case 2: + wpe_button = 3; + break; // Secondary -> Right (context menu) + case 3: + wpe_button = 2; + break; // Tertiary -> Middle + default: + wpe_button = 1; + break; // Default to primary + } + + // WPEPlatform button modifier bits: WPE_MODIFIER_POINTER_BUTTON1 = 1 << 8, etc. + // Button 1 -> bit 8, Button 2 -> bit 9, Button 3 -> bit 10 + const uint32_t button_modifier_bit = 1u << (7 + wpe_button); + + guint32 time = static_cast(g_get_monotonic_time() / 1000); + WPEEventType event_type; + + switch (static_cast(kind)) { + case WpePointerEventKind::Down: + event_type = WPE_EVENT_POINTER_DOWN; + // Update button state BEFORE creating the event + button_state_ |= button_modifier_bit; + break; + case WpePointerEventKind::Up: + event_type = WPE_EVENT_POINTER_UP; + // Update button state AFTER the event (but include in modifiers) + break; + default: + // Ignore enter/leave/cancel etc for button events + return; + } + + // Include button state in modifiers for proper drag detection + WPEModifiers modifiers = static_cast(current_modifiers_ | button_state_); + + // First send a motion event to ensure WebKit has the correct cursor position + WPEEvent* motion_event = wpe_event_pointer_move_new( + WPE_EVENT_POINTER_MOVE, + wpe_view_, + WPE_INPUT_SOURCE_MOUSE, + time, + modifiers, + scaled_x, + scaled_y, + 0.0, + 0.0 + ); + wpe_view_event(wpe_view_, motion_event); + wpe_event_unref(motion_event); + + // Send button event + // CRITICAL: press_count must be 0 for UP events, only non-zero for DOWN events + // (WPEPlatform assertion: !pressCount || type == WPE_EVENT_POINTER_DOWN) + guint press_count = (event_type == WPE_EVENT_POINTER_DOWN) + ? static_cast(clickCount) + : 0; + + WPEEvent* button_event = wpe_event_pointer_button_new( + event_type, + wpe_view_, + WPE_INPUT_SOURCE_MOUSE, + time, + modifiers, + wpe_button, + scaled_x, + scaled_y, + press_count + ); + wpe_view_event(wpe_view_, button_event); + wpe_event_unref(button_event); + + // Clear button state AFTER sending the UP event + if (event_type == WPE_EVENT_POINTER_UP) { + button_state_ &= ~button_modifier_bit; + } + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ == nullptr) + return; + + // Scale coordinates from logical to physical pixels + int scaled_x = static_cast(cursor_x_ * scale_factor_); + int scaled_y = static_cast(cursor_y_ * scale_factor_); + + // Map button: Flutter uses 0=none, 1=primary, 2=secondary, 3=tertiary + // WPE/WebKit uses: 1=Left, 2=Right, 3=Middle (see WebEventFactory.cpp) + // This is a 1:1 mapping for Flutter -> WPE + uint32_t wpe_button; + switch (button) { + case 1: + wpe_button = 1; + break; // Primary -> Left + case 2: + wpe_button = 2; + break; // Secondary -> Right (context menu) + case 3: + wpe_button = 3; + break; // Tertiary -> Middle + default: + wpe_button = 1; + break; // Default to primary + } + + // WPE button modifier bits for tracking pressed buttons in modifiers field + // See wpe_input_pointer_modifier_button* in wpe/input.h: button1=1<<20, button2=1<<21, + // button3=1<<22 + const uint32_t button_modifier_bit = 1u << (19 + wpe_button); + + // First send a motion event to ensure WebKit has the correct cursor position + // This is important because the button event needs to know where the click occurred + { + struct wpe_input_pointer_event motion_event = {}; + motion_event.type = wpe_input_pointer_event_type_motion; + motion_event.time = g_get_monotonic_time() / 1000; + motion_event.x = scaled_x; + motion_event.y = scaled_y; + motion_event.button = 0; + motion_event.state = 0; + motion_event.modifiers = current_modifiers_ | button_state_; + wpe_view_backend_dispatch_pointer_event(wpe_backend_, &motion_event); + } + + struct wpe_input_pointer_event event = {}; + event.time = g_get_monotonic_time() / 1000; + event.x = scaled_x; + event.y = scaled_y; + event.button = wpe_button; + + switch (static_cast(kind)) { + case WpePointerEventKind::Down: + event.type = wpe_input_pointer_event_type_button; + // state=1 means button is pressed (see WebEventFactory: event->state ? MouseDown : MouseUp) + event.state = 1; + button_state_ |= button_modifier_bit; + event.modifiers = current_modifiers_ | button_state_; + break; + case WpePointerEventKind::Up: + event.type = wpe_input_pointer_event_type_button; + // state=0 means button is released + event.state = 0; + button_state_ &= ~button_modifier_bit; + event.modifiers = current_modifiers_ | button_state_; + break; + default: + // Ignore enter/leave/cancel etc for button events + return; + } + + wpe_view_backend_dispatch_pointer_event(wpe_backend_, &event); +#endif +} + +void InAppWebView::SetScrollDelta(double dx, double dy) { + // Hide all popups when scrolling + HideAllPopups(); + +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ == nullptr) + return; + + // No need to scale coordinates from logical to physical pixels as WPEPlatform handles this internally. + double scaled_x = cursor_x_; + double scaled_y = cursor_y_; + + WPEModifiers modifiers = static_cast(current_modifiers_); + guint32 time = static_cast(g_get_monotonic_time() / 1000); + + // Flutter provides delta in logical pixels. + // No need to scale coordinates from logical to physical pixels as WPEPlatform handles this internally. + double delta_x = dx; + double delta_y = dy; + + WPEEvent* event = wpe_event_scroll_new( + wpe_view_, + WPE_INPUT_SOURCE_MOUSE, + time, + modifiers, + delta_x, + delta_y, + TRUE, // precise_deltas - we have exact pixel values + FALSE, // is_stop - this is not a scroll stop event + scaled_x, + scaled_y + ); + wpe_view_event(wpe_view_, event); + wpe_event_unref(event); + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ == nullptr) + return; + + // Scale coordinates from logical to physical pixels + int scaled_x = static_cast(cursor_x_ * scale_factor_); + int scaled_y = static_cast(cursor_y_ * scale_factor_); + + // Use the 2D axis event with smooth scrolling for proper pixel-based scrolling + // The wpe_input_axis_2d_event provides x_axis and y_axis as doubles + // With wpe_input_axis_event_type_motion_smooth | wpe_input_axis_event_type_mask_2d, + // WebKit will use the raw pixel values for smooth scrolling + + struct wpe_input_axis_2d_event event = {}; + event.base.type = static_cast(wpe_input_axis_event_type_motion_smooth | + wpe_input_axis_event_type_mask_2d); + event.base.time = g_get_monotonic_time() / 1000; + event.base.x = scaled_x; + event.base.y = scaled_y; + event.base.modifiers = current_modifiers_; + + // Flutter provides delta in logical pixels, scale to physical and apply sensitivity + // The scale factor converts logical to physical pixels + event.x_axis = dx * scale_factor_; + event.y_axis = dy * scale_factor_; + + wpe_view_backend_dispatch_axis_event(wpe_backend_, &event.base); +#endif +} + +void InAppWebView::SendKeyEvent(int type, int64_t keyCode, int scanCode, int modifiers, + const std::string& characters) { + // Intercept clipboard shortcuts on key down (type=0) + // Modifiers: Control=1, Shift=2, Alt=4, Meta=8 + const bool isCtrl = (modifiers & 1) != 0; + const bool isShift = (modifiers & 2) != 0; + const bool isKeyDown = (type == 0); + + if (isCtrl && isKeyDown && webview_ != nullptr) { + // Check for clipboard and common editing shortcuts + // keyCode is XKB keysym, lowercase letters are 0x61-0x7a + switch (keyCode) { + case 0x63: // 'c' - Copy + copyToClipboard(); + return; // Don't send the key event to WebKit + case 0x78: // 'x' - Cut + cutToClipboard(); + return; + case 0x76: // 'v' - Paste (Ctrl+Shift+V = paste as plain text) + if (isShift) { + pasteAsPlainText(); + } else { + pasteFromClipboard(); + } + return; + case 0x61: // 'a' - Select All + selectAll(); + return; + case 0x7a: // 'z' - Undo (Ctrl+Shift+Z = Redo on some systems) + if (isShift) { + redo(); + } else { + undo(); + } + return; + case 0x79: // 'y' - Redo + redo(); + return; + } + } + + current_modifiers_ = static_cast(modifiers); + +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ == nullptr) + return; + + WPEModifiers wpe_modifiers = static_cast(current_modifiers_); + guint32 time = static_cast(g_get_monotonic_time() / 1000); + + // type: 0=down, 1=up, 2=repeat + WPEEventType event_type; + switch (type) { + case 0: // down + case 2: // repeat (also treated as key down in WPE) + event_type = WPE_EVENT_KEYBOARD_KEY_DOWN; + break; + case 1: // up + event_type = WPE_EVENT_KEYBOARD_KEY_UP; + break; + default: + return; + } + + WPEEvent* event = wpe_event_keyboard_new( + event_type, + wpe_view_, + WPE_INPUT_SOURCE_KEYBOARD, + time, + wpe_modifiers, + static_cast(scanCode), // hardware keycode + static_cast(keyCode) // keyval (XKB keysym) + ); + wpe_view_event(wpe_view_, event); + wpe_event_unref(event); + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ == nullptr) + return; + + struct wpe_input_keyboard_event event = {}; + event.time = g_get_monotonic_time() / 1000; + event.key_code = static_cast(keyCode); + event.hardware_key_code = static_cast(scanCode); + // type: 0=down, 1=up, 2=repeat + event.pressed = (type == 0 || type == 2); + + // Modifiers from Dart are already in WPE format: + // Control=1, Shift=2, Alt=4, Meta=8 + event.modifiers = current_modifiers_; + + wpe_view_backend_dispatch_keyboard_event(wpe_backend_, &event); +#endif +} + +void InAppWebView::SendTouchEvent( + int type, int id, double x, double y, + const std::vector>& touchPoints) { +#ifdef HAVE_WPE_PLATFORM + if (wpe_view_ == nullptr) + return; + + WPEModifiers modifiers = static_cast(current_modifiers_); + guint32 time = static_cast(g_get_monotonic_time() / 1000); + + // Map Dart touch event types to WPE types + // Dart: 0=down, 1=up, 2=move, 3=cancel + WPEEventType event_type; + switch (type) { + case 0: + event_type = WPE_EVENT_TOUCH_DOWN; + break; + case 1: + event_type = WPE_EVENT_TOUCH_UP; + break; + case 2: + event_type = WPE_EVENT_TOUCH_MOVE; + break; + case 3: + event_type = WPE_EVENT_TOUCH_CANCEL; + break; + default: + return; + } + + // For WPEPlatform, we send individual touch events for each point. + // The main touch point is the one that triggered this event. + // No need to scale coordinates from logical to physical pixels as WPEPlatform handles this internally. + double scaled_x = x; + double scaled_y = y; + + WPEEvent* event = wpe_event_touch_new( + event_type, + wpe_view_, + WPE_INPUT_SOURCE_TOUCHSCREEN, + time, + modifiers, + static_cast(id), + scaled_x, + scaled_y + ); + wpe_view_event(wpe_view_, event); + wpe_event_unref(event); + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + if (wpe_backend_ == nullptr) + return; + + // Map Dart touch event types to WPE types + // Dart: 0=down, 1=up, 2=move, 3=cancel + // WPE: wpe_input_touch_event_type_down=1, up=3, motion=2 + enum wpe_input_touch_event_type wpe_type; + switch (type) { + case 0: + wpe_type = wpe_input_touch_event_type_down; + break; + case 1: + wpe_type = wpe_input_touch_event_type_up; + break; + case 2: + wpe_type = wpe_input_touch_event_type_motion; + break; + default: + wpe_type = wpe_input_touch_event_type_null; + break; // cancel + } + + // Build the raw touchpoints array + std::vector raw_points; + raw_points.reserve(touchPoints.size()); + + for (const auto& point : touchPoints) { + struct wpe_input_touch_event_raw raw = {}; + raw.id = std::get<0>(point); + raw.x = static_cast(std::get<1>(point) * scale_factor_); + raw.y = static_cast(std::get<2>(point) * scale_factor_); + + // Map point type + int pointType = std::get<3>(point); + switch (pointType) { + case 0: + raw.type = wpe_input_touch_event_type_down; + break; + case 1: + raw.type = wpe_input_touch_event_type_up; + break; + case 2: + raw.type = wpe_input_touch_event_type_motion; + break; + default: + raw.type = wpe_input_touch_event_type_null; + break; + } + raw.time = g_get_monotonic_time() / 1000; + + raw_points.push_back(raw); + } + + // Build the touch event + struct wpe_input_touch_event event = {}; + event.touchpoints = raw_points.data(); + event.touchpoints_length = raw_points.size(); + event.type = wpe_type; + event.id = id; + event.time = g_get_monotonic_time() / 1000; + event.modifiers = current_modifiers_; + + wpe_view_backend_dispatch_touch_event(wpe_backend_, &event); +#endif +} + +// === Pixel Buffer Access === + +size_t InAppWebView::GetPixelBufferSize(uint32_t* out_width, uint32_t* out_height) const { + // With WPE + FDO, we typically use DMA-BUF export instead of CPU copy + // This is a fallback for when DMA-BUF is not available + std::lock_guard lock(buffer_swap_mutex_); + + size_t read_idx = read_buffer_index_.load(std::memory_order_acquire); + const auto& buffer = pixel_buffers_[read_idx]; + + if (out_width) + *out_width = static_cast(buffer.width); + if (out_height) + *out_height = static_cast(buffer.height); + + return buffer.data.size(); +} + +bool InAppWebView::CopyPixelBufferTo(uint8_t* dst, size_t dst_size, uint32_t* out_width, + uint32_t* out_height) const { + std::lock_guard lock(buffer_swap_mutex_); + + size_t read_idx = read_buffer_index_.load(std::memory_order_acquire); + const auto& buffer = pixel_buffers_[read_idx]; + + if (buffer.data.empty() || dst_size < buffer.data.size()) { + return false; + } + + // Use SIMD-optimized memory copy for better performance + FastMemcpy(dst, buffer.data.data(), buffer.data.size()); + + if (out_width) + *out_width = static_cast(buffer.width); + if (out_height) + *out_height = static_cast(buffer.height); + + return true; +} + +bool InAppWebView::HasDmaBufExport() const { +#ifdef HAVE_WPE_PLATFORM + std::lock_guard lock(wpe_buffer_mutex_); + return current_egl_image_ != nullptr; +#elif defined(HAVE_WPE_BACKEND_LEGACY) + std::lock_guard lock(exported_image_mutex_); + return exported_image_ != nullptr; +#else + return false; +#endif +} + +bool InAppWebView::GetDmaBufFd(int* fd, uint32_t* stride, uint32_t* width, uint32_t* height) const { +#ifdef HAVE_WPE_BACKEND_LEGACY + std::lock_guard lock(exported_image_mutex_); + if (exported_image_ == nullptr) { + return false; + } + + // Get DMA-BUF file descriptor from the exported image + // This allows zero-copy sharing with Flutter's texture system + // Note: The actual API depends on the WPE version + // This is a simplified version + + if (width) + *width = static_cast(width_); + if (height) + *height = static_cast(height_); + + // In a real implementation, you'd get the DMA-BUF FD from the EGL image + // For now, return false to indicate not implemented + return false; +#else + // WPEPlatform uses a different rendering model + return false; +#endif +} + +void* InAppWebView::GetCurrentEglImage(uint32_t* out_width, uint32_t* out_height) const { +#ifdef HAVE_WPE_PLATFORM + // WPEPlatform: Return the EGL image from our buffer-rendered callback + std::lock_guard lock(wpe_buffer_mutex_); + + if (current_egl_image_ == nullptr) { + if (out_width) + *out_width = 0; + if (out_height) + *out_height = 0; + return nullptr; + } + + if (out_width) + *out_width = current_buffer_width_; + if (out_height) + *out_height = current_buffer_height_; + + return current_egl_image_; + +#elif defined(HAVE_WPE_BACKEND_LEGACY) + // Protect exported_image_ access - OnExportDmaBuf may be called from WPE's thread + std::lock_guard lock(exported_image_mutex_); + + if (exported_image_ == nullptr) { + if (out_width) + *out_width = 0; + if (out_height) + *out_height = 0; + return nullptr; + } + + // Get dimensions from the exported image + uint32_t img_width = wpe_fdo_egl_exported_image_get_width(exported_image_); + uint32_t img_height = wpe_fdo_egl_exported_image_get_height(exported_image_); + + if (out_width) + *out_width = img_width; + if (out_height) + *out_height = img_height; + + // Return the EGL image handle (EGLImageKHR) + return wpe_fdo_egl_exported_image_get_egl_image(exported_image_); +#else + // No backend available + if (out_width) + *out_width = 0; + if (out_height) + *out_height = 0; + return nullptr; +#endif +} + +void InAppWebView::SetOnFrameAvailable(std::function callback) { + on_frame_available_ = std::move(callback); + +#ifdef HAVE_WPE_PLATFORM + // Force WPE to render a new frame by triggering a resize. + // This is needed because: + // 1. The first frame may have been rendered before this callback was set + // 2. We release buffers immediately in OnWpePlatformBufferRendered, so + // old EGL images may be invalid + // 3. A resize notification causes WPE to re-render with the current content + // + // We use g_idle_add to defer this slightly, ensuring the texture registration + // is complete before we trigger the new frame. + if (on_frame_available_ && wpe_toplevel_ != nullptr) { + WPEToplevel* toplevel = wpe_toplevel_; + int w = width_; + int h = height_; + g_idle_add_full(G_PRIORITY_HIGH, [](gpointer user_data) -> gboolean { + auto* data = static_cast*>(user_data); + WPEToplevel* tl = std::get<0>(*data); + int width = std::get<1>(*data); + int height = std::get<2>(*data); + // Trigger a resize to force WPE to render a new frame + if (tl != nullptr) { + wpe_toplevel_resize(tl, width, height); + } + delete data; + return G_SOURCE_REMOVE; + }, new std::tuple(toplevel, w, h), nullptr); + } +#endif +} + +void InAppWebView::SetOnCursorChanged(std::function callback) { + on_cursor_changed_ = std::move(callback); +} + +void InAppWebView::SetOnProgressChanged(std::function callback) { + on_progress_changed_ = std::move(callback); +} + +void InAppWebView::SetOnNavigationStateChanged(std::function callback) { + on_navigation_state_changed_ = std::move(callback); +} + +void InAppWebView::OnShouldOverrideUrlLoadingDecision(int64_t decision_id, bool allow) { + auto it = pending_policy_decisions_.find(decision_id); + if (it == pending_policy_decisions_.end()) { + return; + } + + WebKitPolicyDecision* decision = it->second; + if (allow) { + webkit_policy_decision_use(decision); + } else { + webkit_policy_decision_ignore(decision); + } + + g_object_unref(decision); + pending_policy_decisions_.erase(it); +} + +// === WebKit Signal Handlers === + +void InAppWebView::OnLoadChanged(WebKitWebView* web_view, WebKitLoadEvent load_event, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Check if WebView is still valid (WebProcess may have crashed) + if (!WEBKIT_IS_WEB_VIEW(web_view)) { + return; + } + + if (self->channel_delegate_ == nullptr) { + return; + } + + switch (load_event) { + case WEBKIT_LOAD_STARTED: { + // Hide all popups when a new page starts loading + self->HideAllPopups(); + + std::string current_url = self->getUrl().value_or(""); + self->channel_delegate_->onLoadStart(current_url); + break; + } + case WEBKIT_LOAD_REDIRECTED: + // Redirects are handled internally by WebKit + break; + case WEBKIT_LOAD_COMMITTED: + // Notify that page content is starting to be visible + self->channel_delegate_->onPageCommitVisible(self->getUrl().value_or("")); + break; + case WEBKIT_LOAD_FINISHED: + self->channel_delegate_->onLoadStop(self->getUrl().value_or("")); + break; + default: + break; + } +} + +gboolean InAppWebView::OnDecidePolicy(WebKitWebView* web_view, WebKitPolicyDecision* decision, + WebKitPolicyDecisionType decision_type, gpointer user_data) { + auto* self = static_cast(user_data); + + // Handle response policy decisions (for onNavigationResponse and downloads) + if (decision_type == WEBKIT_POLICY_DECISION_TYPE_RESPONSE) { + auto* response_decision = WEBKIT_RESPONSE_POLICY_DECISION(decision); + + // Get response information for onNavigationResponse + WebKitURIResponse* response = webkit_response_policy_decision_get_response(response_decision); + gboolean is_mime_type_supported = webkit_response_policy_decision_is_mime_type_supported(response_decision); + gboolean is_main_frame = webkit_response_policy_decision_is_main_frame_main_resource(response_decision); + + const gchar* uri = webkit_uri_response_get_uri(response); + const gchar* mimeType = webkit_uri_response_get_mime_type(response); + gint64 contentLength = webkit_uri_response_get_content_length(response); + guint statusCode = webkit_uri_response_get_status_code(response); + + // If channel_delegate exists and settings permit, send onNavigationResponse event + if (self->channel_delegate_ && self->settings_ && self->settings_->useOnNavigationResponse) { + // Keep decision alive for async callback + g_object_ref(decision); + + auto callback = std::make_unique(); + + callback->nonNullSuccess = [self, decision, is_mime_type_supported](int action) -> bool { + // NavigationResponseAction: CANCEL=0, ALLOW=1, DOWNLOAD=2 + switch (action) { + case 0: // CANCEL + webkit_policy_decision_ignore(decision); + break; + case 2: // DOWNLOAD + webkit_policy_decision_download(decision); + break; + case 1: // ALLOW (default) + default: + if (!is_mime_type_supported && self->settings_ && self->settings_->useOnDownloadStart) { + // WebKit can't display this - convert to download + webkit_policy_decision_download(decision); + } else { + webkit_policy_decision_use(decision); + } + break; + } + g_object_unref(decision); + return false; // Don't run defaultBehaviour + }; + + callback->defaultBehaviour = [decision, is_mime_type_supported, self](const std::optional& action) { + // Default: allow navigation (or download if MIME not supported and download enabled) + if (!is_mime_type_supported && self->settings_ && self->settings_->useOnDownloadStart) { + webkit_policy_decision_download(decision); + } else { + webkit_policy_decision_use(decision); + } + g_object_unref(decision); + }; + + callback->error = [decision, is_mime_type_supported, self](const std::string& code, const std::string& message) { + debugLog("Error in onNavigationResponse: " + code + " - " + message); + // On error, allow navigation + if (!is_mime_type_supported && self->settings_ && self->settings_->useOnDownloadStart) { + webkit_policy_decision_download(decision); + } else { + webkit_policy_decision_use(decision); + } + g_object_unref(decision); + }; + + self->channel_delegate_->onNavigationResponse( + uri != nullptr ? std::string(uri) : "", + mimeType != nullptr ? std::optional(mimeType) : std::nullopt, + contentLength, + static_cast(statusCode), + is_main_frame != FALSE, + is_mime_type_supported != FALSE, + std::move(callback)); + + return TRUE; // We're handling this asynchronously + } + + // Fallback: check if the response should trigger a download (no onNavigationResponse handler) + // This happens when: + // 1. Content-Disposition header is "attachment" + // 2. WebKit can't display the MIME type + if (!is_mime_type_supported) { + // WebKit can't display this MIME type - convert to download + // This will trigger the download-started signal on NetworkSession + if (self->settings_ && self->settings_->useOnDownloadStart) { + webkit_policy_decision_download(decision); + return TRUE; + } + } + + return FALSE; // Let WebKit handle the response normally + } + + if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { + return FALSE; // Let WebKit handle other decision types + } + + auto* nav_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision); + auto* nav_action = webkit_navigation_policy_decision_get_navigation_action(nav_decision); + auto* request = webkit_navigation_action_get_request(nav_action); + const gchar* uri = webkit_uri_request_get_uri(request); + + if (self->settings_ && self->settings_->useShouldOverrideUrlLoading) { + if (self->channel_delegate_) { + int64_t decision_id = self->next_decision_id_++; + g_object_ref(decision); + self->pending_policy_decisions_[decision_id] = decision; + + // Best-effort main frame detection: frame name is usually null/empty for main frame. + bool is_for_main_frame = true; + const gchar* frame_name = webkit_navigation_action_get_frame_name(nav_action); + if (frame_name != nullptr && frame_name[0] != '\0') { + is_for_main_frame = false; + } + + // Create URLRequest + auto urlRequest = std::make_shared( + uri != nullptr ? std::optional(uri) : std::nullopt, + std::optional("GET"), + std::nullopt, // headers + std::nullopt // body + ); + + // Map WebKit navigation type to our enum + WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(nav_action); + std::optional navActionType; + switch (nav_type) { + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: + navActionType = NavigationActionType::linkActivated; + break; + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: + navActionType = NavigationActionType::backForward; + break; + case WEBKIT_NAVIGATION_TYPE_RELOAD: + navActionType = NavigationActionType::reload; + break; + default: + navActionType = NavigationActionType::other; + break; + } + + // Create NavigationAction + auto navigationAction = std::make_shared( + urlRequest, is_for_main_frame, + std::nullopt, // isRedirect - not easily available in WebKit + navActionType); + + // Create callback to handle the response + auto callback = std::make_unique(); + + // CRITICAL: Set error handler to prevent navigation from being blocked on channel errors + // Allow navigation on error to prevent page from being stuck + callback->error = [self, decision_id](const std::string& code, const std::string& message) { + g_warning("shouldOverrideUrlLoading channel error: %s - %s", code.c_str(), message.c_str()); + // Allow navigation on error to prevent page from being stuck + self->OnShouldOverrideUrlLoadingDecision(decision_id, true); + }; + + callback->defaultBehaviour = + [self, decision_id](const std::optional result) { + bool allow = result.has_value() && result.value() == NavigationActionPolicy::allow; + self->OnShouldOverrideUrlLoadingDecision(decision_id, allow); + }; + + // Notify Dart side with callback + self->channel_delegate_->shouldOverrideUrlLoading(navigationAction, std::move(callback)); + + return TRUE; // We're handling this asynchronously + } + } + + return FALSE; // Let WebKit proceed with navigation +} + +void InAppWebView::OnNotifyEstimatedLoadProgress(GObject* object, GParamSpec* pspec, + gpointer user_data) { + auto* self = static_cast(user_data); + + double progress = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(object)); + int progress_percent = static_cast(progress * 100); + + // Notify Dart via channel delegate + if (self->channel_delegate_) { + self->channel_delegate_->onProgressChanged(progress_percent); + } + + // Notify native progress callback (for InAppBrowser) + if (self->on_progress_changed_) { + self->on_progress_changed_(progress); + } +} + +void InAppWebView::OnNotifyTitle(GObject* object, GParamSpec* pspec, gpointer user_data) { + auto* self = static_cast(user_data); + if (self->channel_delegate_ == nullptr) + return; + + const gchar* title = webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)); + if (title != nullptr) { + self->channel_delegate_->onTitleChanged(std::string(title)); + } +} + +void InAppWebView::OnNotifyUri(GObject* object, GParamSpec* pspec, gpointer user_data) { + auto* self = static_cast(user_data); + if (self->channel_delegate_ == nullptr) + return; + + const gchar* uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)); + std::optional url = uri ? std::optional(uri) : std::nullopt; + + // isReload is not easily detectable - pass false by default + self->channel_delegate_->onUpdateVisitedHistory(url, false); +} + +gboolean InAppWebView::OnLoadFailed(WebKitWebView* web_view, WebKitLoadEvent load_event, + gchar* failing_uri, GError* error, gpointer user_data) { + auto* self = static_cast(user_data); + + if (self->channel_delegate_ == nullptr) + return FALSE; + + // Create WebResourceRequest for the failing URL + auto request = std::make_shared( + failing_uri ? std::optional(failing_uri) : std::nullopt, + std::optional("GET"), // Default method + std::nullopt, // headers + std::optional(true) // isForMainFrame - load failures are typically main frame + ); + + // Create WebResourceError from GError + auto webError = + std::make_shared(error ? std::string(error->message) : "Unknown error", + error ? static_cast(error->code) : -1); + + self->channel_delegate_->onReceivedError(request, webError); + + return FALSE; +} + +gboolean InAppWebView::OnLoadFailedWithTlsErrors(WebKitWebView* web_view, gchar* failing_uri, + GTlsCertificate* certificate, + GTlsCertificateFlags errors, gpointer user_data) { + auto* self = static_cast(user_data); + + if (!self->channel_delegate_) { + return FALSE; + } + + // Create the challenge from TLS error info + auto challenge = ServerTrustChallenge::fromTlsError( + std::string(failing_uri != nullptr ? failing_uri : ""), + certificate, errors); + + // Keep a reference to the certificate and web view for later use + g_object_ref(certificate); + g_object_ref(web_view); + + auto callback = std::make_unique(); + callback->nonNullSuccess = [web_view, failing_uri, certificate]( + const ServerTrustAuthResponse& response) -> bool { + if (response.action == ServerTrustAuthResponseAction::PROCEED) { + // Allow the certificate for this host + // Extract host from failing_uri + std::string host = get_host_from_url(std::string(failing_uri != nullptr ? failing_uri : "")); + if (!host.empty()) { + // Get the network session from the web view + WebKitNetworkSession* network_session = webkit_web_view_get_network_session(web_view); + webkit_network_session_allow_tls_certificate_for_host(network_session, certificate, host.c_str()); + // Reload the page to retry with the allowed certificate + webkit_web_view_reload(web_view); + } + } + g_object_unref(certificate); + g_object_unref(web_view); + return false; + }; + + callback->defaultBehaviour = [certificate, web_view](const std::optional& response) { + // Default: cancel the request (do nothing, WebKit will handle the error) + g_object_unref(certificate); + g_object_unref(web_view); + }; + + callback->error = [certificate, web_view](const std::string& code, const std::string& message) { + debugLog("Error in onReceivedServerTrustAuthRequest: " + code + " - " + message); + g_object_unref(certificate); + g_object_unref(web_view); + }; + + self->channel_delegate_->onReceivedServerTrustAuthRequest(std::move(challenge), std::move(callback)); + + // Return TRUE to indicate we're handling this asynchronously + return TRUE; +} + +void InAppWebView::OnCloseRequest(WebKitWebView* web_view, gpointer user_data) { + auto* self = static_cast(user_data); + if (self->channel_delegate_) { + self->channel_delegate_->onCloseWindow(); + } +} + +// WPE WebKit create signal handler - returns WebKitWebView* (not GtkWidget*) +// +// In WPE WebKit, creating a new WebView requires a WebKitWebViewBackend which +// is tightly coupled with the WPE FDO exportable pipeline. Unlike WebKitGTK, +// we cannot easily create a related view without the full backend setup. +// +// Multi-Window Support +// +// When JavaScript calls window.open() or a link has target="_blank", WebKit emits +// the "create" signal. We must create a new WebView that shares the web process +// with the parent (for session/cookies) and return it to WebKit. +// +// The pattern used here: +// 1. Get a window ID from the manager +// 2. Create a new InAppWebView with the parent webview as "related view" +// 3. Store the InAppWebView in windowWebViews for later attachment by Dart +// 4. Notify Dart via onCreateWindow callback +// 5. Return the new WebKitWebView* to WebKit (so window.open() returns a real window) +// +// If Dart doesn't handle it, the default behaviour loads the URL in the parent view. +WebKitWebView* InAppWebView::OnCreateWebView(WebKitWebView* web_view, + WebKitNavigationAction* navigation_action, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Check if JavaScript can open windows automatically + if (!self->settings_ || !self->settings_->javaScriptCanOpenWindowsAutomatically) { + // Default to allowing navigation in current window + WebKitURIRequest* request = webkit_navigation_action_get_request(navigation_action); + if (request != nullptr) { + const gchar* uri = webkit_uri_request_get_uri(request); + if (uri != nullptr && self->webview_ != nullptr) { + webkit_web_view_load_uri(self->webview_, uri); + } + } + return nullptr; + } + + // Get window ID from manager + int64_t windowId = 0; + if (self->manager_ != nullptr) { + windowId = self->manager_->GetNextWindowId(); + } else { + // Fallback to static counter if no manager + static int64_t window_autoincrement_id = 0; + windowId = ++window_autoincrement_id; + } + + // Get the URL from the navigation action + WebKitURIRequest* request = webkit_navigation_action_get_request(navigation_action); + std::optional url_to_load; + if (request != nullptr) { + const gchar* uri = webkit_uri_request_get_uri(request); + if (uri != nullptr) { + url_to_load = std::string(uri); + } + } + + // Create a new InAppWebView that shares the web process with the parent + // This is the key difference from the old implementation - we actually create + // a real WebView that WebKit can use for the popup window + InAppWebViewCreationParams windowParams; + windowParams.id = windowId; + windowParams.gtkWindow = self->gtk_window_; + windowParams.flView = self->fl_view_; // Pass FlView for focus restoration + windowParams.manager = self->manager_; + windowParams.initialSettings = self->settings_; // Share parent settings + windowParams.windowId = windowId; + windowParams.relatedWebView = self->webview_; // Share web process with parent + + // Create the new InAppWebView for the popup window + auto windowWebView = std::make_unique( + self->registrar_, nullptr, windowId, windowParams); + + // Get the WebKitWebView* to return to WebKit BEFORE moving the unique_ptr + WebKitWebView* newWebKitWebView = windowWebView->webview(); + + if (newWebKitWebView == nullptr) { + errorLog("InAppWebView::OnCreateWebView: Failed to create popup WebView"); + // Fall back to loading in parent + if (url_to_load.has_value() && self->webview_ != nullptr) { + webkit_web_view_load_uri(self->webview_, url_to_load.value().c_str()); + } + return nullptr; + } + + // Store the InAppWebView in the manager for later attachment by Dart + if (self->manager_ != nullptr) { + auto transport = std::make_unique(std::move(windowWebView), url_to_load); + self->manager_->AddWindowWebView(windowId, std::move(transport)); + } + + auto createWindowAction = + std::make_unique(navigation_action, windowId, nullptr); + + auto callback = std::make_unique(); + + callback->nonNullSuccess = [](bool handledByClient) { return !handledByClient; }; + + // Capture for cleanup on default behaviour + auto* manager = self->manager_; + auto* parentWebview = self->webview_; + std::string captured_url = url_to_load.value_or(""); + int64_t capturedWindowId = windowId; + + callback->defaultBehaviour = [manager, parentWebview, captured_url, capturedWindowId](std::optional) { + // If the Dart side doesn't handle the window, clean up and load in current view + if (manager != nullptr) { + manager->RemoveWindowWebView(capturedWindowId); + } + // Load the URL in the parent view instead + if (!captured_url.empty() && parentWebview != nullptr) { + webkit_web_view_load_uri(parentWebview, captured_url.c_str()); + } + }; + + if (self->channel_delegate_) { + self->channel_delegate_->onCreateWindow(std::move(createWindowAction), std::move(callback)); + } else { + callback->defaultBehaviour(std::nullopt); + } + + // Return the new WebKitWebView* to WebKit + // This allows window.open() to return a real window object to JavaScript + return newWebKitWebView; +} + +gboolean InAppWebView::OnScriptDialog(WebKitWebView* web_view, WebKitScriptDialog* dialog, + gpointer user_data) { + auto* self = static_cast(user_data); + + WebKitScriptDialogType dialogType = webkit_script_dialog_get_dialog_type(dialog); + const gchar* message = webkit_script_dialog_get_message(dialog); + + if (!self->channel_delegate_) { + return FALSE; // Use default WebKit dialog handling + } + + std::optional url; + const gchar* uri = webkit_web_view_get_uri(web_view); + if (uri != nullptr) { + url = std::string(uri); + } + + std::string messageStr = message ? std::string(message) : ""; + + // Keep a reference to the dialog for async handling + webkit_script_dialog_ref(dialog); + int64_t dialogId = self->next_dialog_id_++; + self->pending_script_dialogs_[dialogId] = dialog; + + switch (dialogType) { + case WEBKIT_SCRIPT_DIALOG_ALERT: { + auto request = std::make_unique(url, messageStr, true); + auto callback = std::make_unique(); + + auto* pendingDialogs = &self->pending_script_dialogs_; + int64_t capturedId = dialogId; + + callback->nonNullSuccess = [](JsAlertResponse response) { return !response.handledByClient; }; + + callback->defaultBehaviour = [pendingDialogs, capturedId](std::optional) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + }; + + self->channel_delegate_->onJsAlert(std::move(request), std::move(callback)); + break; + } + + case WEBKIT_SCRIPT_DIALOG_CONFIRM: { + auto request = std::make_unique(url, messageStr, true); + auto callback = std::make_unique(); + + auto* pendingDialogs = &self->pending_script_dialogs_; + int64_t capturedId = dialogId; + + callback->nonNullSuccess = [pendingDialogs, capturedId](JsConfirmResponse response) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_confirm_set_confirmed( + it->second, response.action == JsConfirmResponseAction::CONFIRM); + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingDialogs, capturedId](std::optional) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_confirm_set_confirmed(it->second, FALSE); + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + }; + + self->channel_delegate_->onJsConfirm(std::move(request), std::move(callback)); + break; + } + + case WEBKIT_SCRIPT_DIALOG_PROMPT: { + const gchar* defaultValue = webkit_script_dialog_prompt_get_default_text(dialog); + std::optional defaultValueStr; + if (defaultValue != nullptr) { + defaultValueStr = std::string(defaultValue); + } + + auto request = std::make_unique(url, messageStr, defaultValueStr, true); + auto callback = std::make_unique(); + + auto* pendingDialogs = &self->pending_script_dialogs_; + int64_t capturedId = dialogId; + + callback->nonNullSuccess = [pendingDialogs, capturedId](JsPromptResponse response) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + if (response.action == JsPromptResponseAction::CONFIRM && response.value.has_value()) { + webkit_script_dialog_prompt_set_text(it->second, response.value.value().c_str()); + } + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingDialogs, capturedId](std::optional) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + }; + + self->channel_delegate_->onJsPrompt(std::move(request), std::move(callback)); + break; + } + + case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM: { + auto callback = std::make_unique(); + + auto* pendingDialogs = &self->pending_script_dialogs_; + int64_t capturedId = dialogId; + + callback->nonNullSuccess = [pendingDialogs, capturedId](JsBeforeUnloadResponse response) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_confirm_set_confirmed(it->second, response.shouldAllowNavigation); + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingDialogs, + capturedId](std::optional) { + auto it = pendingDialogs->find(capturedId); + if (it != pendingDialogs->end()) { + webkit_script_dialog_confirm_set_confirmed(it->second, TRUE); + webkit_script_dialog_close(it->second); + webkit_script_dialog_unref(it->second); + pendingDialogs->erase(it); + } + }; + + self->channel_delegate_->onJsBeforeUnload( + url, messageStr.empty() ? std::nullopt : std::make_optional(messageStr), + std::move(callback)); + break; + } + + default: + // Unknown dialog type, let WebKit handle it + webkit_script_dialog_unref(dialog); + self->pending_script_dialogs_.erase(dialogId); + return FALSE; + } + + return TRUE; // We're handling the dialog +} + +gboolean InAppWebView::OnPermissionRequest(WebKitWebView* web_view, + WebKitPermissionRequest* request, gpointer user_data) { + auto* self = static_cast(user_data); + + if (!self->channel_delegate_) { + webkit_permission_request_deny(request); + return TRUE; + } + + auto resourceTypes = PermissionRequest::getResourceTypes(request); + if (resourceTypes.empty()) { + webkit_permission_request_deny(request); + return TRUE; + } + + std::optional origin; + const gchar* uri = webkit_web_view_get_uri(web_view); + if (uri != nullptr) { + origin = std::string(uri); + } + + auto permRequest = std::make_unique(origin, resourceTypes); + + // Keep reference to the WebKit request + g_object_ref(request); + int64_t requestId = self->next_permission_id_++; + self->pending_permission_requests_[requestId] = request; + + auto callback = std::make_unique(); + + auto* pendingRequests = &self->pending_permission_requests_; + int64_t capturedId = requestId; + + callback->nonNullSuccess = [pendingRequests, capturedId](PermissionResponse response) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + if (response.action == PermissionResponseAction::GRANT) { + webkit_permission_request_allow(it->second); + } else { + webkit_permission_request_deny(it->second); + } + g_object_unref(it->second); + pendingRequests->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingRequests, capturedId](std::optional) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + webkit_permission_request_deny(it->second); + g_object_unref(it->second); + pendingRequests->erase(it); + } + }; + + self->channel_delegate_->onPermissionRequest(std::move(permRequest), std::move(callback)); + + return TRUE; // We're handling the request +} + +gboolean InAppWebView::OnAuthenticate(WebKitWebView* web_view, WebKitAuthenticationRequest* request, + gpointer user_data) { + auto* self = static_cast(user_data); + + if (!self->channel_delegate_) { + webkit_authentication_request_cancel(request); + return TRUE; + } + + const gchar* host = webkit_authentication_request_get_host(request); + guint port = webkit_authentication_request_get_port(request); + const gchar* realm = webkit_authentication_request_get_realm(request); + WebKitAuthenticationScheme scheme = webkit_authentication_request_get_scheme(request); + gboolean isProxy = webkit_authentication_request_is_for_proxy(request); + gboolean isRetry = webkit_authentication_request_is_retry(request); + + // Build protection space for URL credential lookup + URLProtectionSpace protectionSpace(host ? std::string(host) : "", static_cast(port), + std::nullopt, // protocol not directly available + realm ? std::make_optional(std::string(realm)) : std::nullopt, + URLProtectionSpace::fromWebKitScheme(scheme), isProxy); + + // Check if this is a client certificate request + if (scheme == WEBKIT_AUTHENTICATION_SCHEME_CLIENT_CERTIFICATE_REQUESTED) { + // Handle client certificate request + auto challenge = std::make_unique(protectionSpace, isProxy); + + // Keep reference to the request + g_object_ref(request); + int64_t requestId = self->next_auth_id_++; + self->pending_auth_requests_[requestId] = request; + + auto callback = std::make_unique(); + + auto* pendingRequests = &self->pending_auth_requests_; + int64_t capturedId = requestId; + + callback->nonNullSuccess = [pendingRequests, capturedId](ClientCertResponse response) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + switch (response.action) { + case ClientCertResponseAction::PROCEED: { + // WPE WebKit's webkit_credential_new_for_certificate() API (since 2.34) + // allows providing a client certificate. + // GTlsCertificate constructors (from docs.gtk.org/gio/class.TlsCertificate.html): + // - g_tls_certificate_new_from_file: PEM file containing cert + optionally private key + // - g_tls_certificate_new_from_file_with_password: Password-protected file (since 2.72) + // - g_tls_certificate_new_from_files: Separate cert and key files + // - g_tls_certificate_new_from_pkcs12: PKCS#12 data with password (since 2.72) + if (response.certificatePath.has_value() && !response.certificatePath->empty()) { + GError* error = nullptr; + GTlsCertificate* cert = nullptr; + + std::string keyStoreType = response.keyStoreType.value_or(""); + std::string certPath = response.certificatePath.value(); + std::optional password = response.certificatePassword; + + if (keyStoreType == "PKCS12" || keyStoreType == "pkcs12") { + // For PKCS12, we need to read the file and use g_tls_certificate_new_from_pkcs12 + // which requires GLib 2.72+ + #if GLIB_CHECK_VERSION(2, 72, 0) + // Read the PKCS12 file + gchar* data = nullptr; + gsize length = 0; + if (g_file_get_contents(certPath.c_str(), &data, &length, &error)) { + cert = g_tls_certificate_new_from_pkcs12( + reinterpret_cast(data), + length, + password.has_value() ? password->c_str() : nullptr, + &error); + g_free(data); + } + #else + debugLog("PKCS12 certificates require GLib 2.72+. Trying PEM fallback..."); + // Fallback to trying as PEM + cert = g_tls_certificate_new_from_file(certPath.c_str(), &error); + #endif + } else if (password.has_value() && !password->empty()) { + // Password-protected file (e.g., encrypted PEM) - requires GLib 2.72+ + #if GLIB_CHECK_VERSION(2, 72, 0) + cert = g_tls_certificate_new_from_file_with_password( + certPath.c_str(), + password->c_str(), + &error); + #else + debugLog("Password-protected certificates require GLib 2.72+. Trying without password..."); + cert = g_tls_certificate_new_from_file(certPath.c_str(), &error); + #endif + } else { + // Standard PEM file (certificate + optional private key in same file) + cert = g_tls_certificate_new_from_file(certPath.c_str(), &error); + } + + if (cert != nullptr) { + // Create credential from certificate + WebKitCredential* credential = webkit_credential_new_for_certificate( + cert, WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION); + webkit_authentication_request_authenticate(it->second, credential); + webkit_credential_free(credential); + g_object_unref(cert); + } else { + if (error != nullptr) { + debugLog("Failed to load client certificate: " + std::string(error->message)); + g_error_free(error); + } + webkit_authentication_request_cancel(it->second); + } + } else { + // No certificate path provided, cancel + debugLog("Client certificate PROCEED without certificate path - canceling"); + webkit_authentication_request_cancel(it->second); + } + break; + } + + case ClientCertResponseAction::IGNORE: + // Ignore means don't handle this request (let WebKit handle or retry later) + webkit_authentication_request_cancel(it->second); + break; + + case ClientCertResponseAction::CANCEL: + default: + webkit_authentication_request_cancel(it->second); + break; + } + + g_object_unref(it->second); + pendingRequests->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingRequests, capturedId](std::optional) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + webkit_authentication_request_cancel(it->second); + g_object_unref(it->second); + pendingRequests->erase(it); + } + }; + + self->channel_delegate_->onReceivedClientCertRequest(std::move(challenge), std::move(callback)); + + return TRUE; // We're handling the request + } + + // Build ProtectionSpace for libsecret lookup (for HTTP auth) + ProtectionSpace credProtectionSpace; + credProtectionSpace.host = host ? std::string(host) : ""; + credProtectionSpace.port = static_cast(port); + credProtectionSpace.realm = realm ? std::make_optional(std::string(realm)) : std::nullopt; + // Map scheme to protocol for libsecret + if (scheme == WEBKIT_AUTHENTICATION_SCHEME_HTTP_BASIC || + scheme == WEBKIT_AUTHENTICATION_SCHEME_HTTP_DIGEST || + scheme == WEBKIT_AUTHENTICATION_SCHEME_DEFAULT) { + credProtectionSpace.protocol = "http"; + } + + auto challenge = std::make_unique(protectionSpace, isRetry); + + // Keep reference to the request + g_object_ref(request); + int64_t requestId = self->next_auth_id_++; + self->pending_auth_requests_[requestId] = request; + + auto callback = std::make_unique(); + + auto* pendingRequests = &self->pending_auth_requests_; + int64_t capturedId = requestId; + ProtectionSpace capturedPs = credProtectionSpace; + PluginInstance* capturedPlugin = self->plugin_; + + callback->nonNullSuccess = [pendingRequests, capturedId, capturedPs, capturedPlugin](HttpAuthResponse response) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + switch (response.action) { + case HttpAuthResponseAction::PROCEED: + if (response.username.has_value() && response.password.has_value()) { + WebKitCredential* credential = webkit_credential_new( + response.username.value().c_str(), response.password.value().c_str(), + response.permanentPersistence ? WEBKIT_CREDENTIAL_PERSISTENCE_PERMANENT + : WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION); + webkit_authentication_request_authenticate(it->second, credential); + + // Save credential to libsecret if permanent persistence requested + if (response.permanentPersistence) { + auto* credDb = capturedPlugin ? capturedPlugin->credentialDatabase : nullptr; + if (credDb != nullptr) { + Credential cred(response.username.value(), response.password.value()); + credDb->setHttpAuthCredential(capturedPs, cred); + } + } + + webkit_credential_free(credential); + } else { + webkit_authentication_request_cancel(it->second); + } + break; + + case HttpAuthResponseAction::USE_SAVED_CREDENTIAL: { + // Look up credential from our secure storage + auto* credDb = capturedPlugin ? capturedPlugin->credentialDatabase : nullptr; + std::optional savedCred = std::nullopt; + + if (credDb != nullptr) { + // Try to get a saved credential + savedCred = credDb->lookupFirstCredential(capturedPs); + } + + // Fall back to WebKit's proposed credential if we don't have one + if (!savedCred.has_value()) { + WebKitCredential* proposed = webkit_authentication_request_get_proposed_credential(it->second); + if (proposed != nullptr) { + const gchar* username = webkit_credential_get_username(proposed); + const gchar* password = webkit_credential_get_password(proposed); + if (username != nullptr && password != nullptr) { + savedCred = Credential(username, password); + } + webkit_credential_free(proposed); + } + } + + if (savedCred.has_value()) { + WebKitCredential* credential = webkit_credential_new( + savedCred->username.c_str(), + savedCred->password.c_str(), + WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION); + webkit_authentication_request_authenticate(it->second, credential); + webkit_credential_free(credential); + } else { + // No saved credential found, cancel + webkit_authentication_request_cancel(it->second); + } + break; + } + + case HttpAuthResponseAction::CANCEL: + default: + webkit_authentication_request_cancel(it->second); + break; + } + + g_object_unref(it->second); + pendingRequests->erase(it); + } + return false; + }; + + callback->defaultBehaviour = [pendingRequests, capturedId](std::optional) { + auto it = pendingRequests->find(capturedId); + if (it != pendingRequests->end()) { + webkit_authentication_request_cancel(it->second); + g_object_unref(it->second); + pendingRequests->erase(it); + } + }; + + self->channel_delegate_->onReceivedHttpAuthRequest(std::move(challenge), std::move(callback)); + + return TRUE; // We're handling the request +} + +// NOTE: WPE WebKit renders offscreen without a GDK window. +// We use the Flutter window's GDK window to display the native GTK context menu. + +gboolean InAppWebView::OnContextMenu(WebKitWebView* web_view, WebKitContextMenu* context_menu, + WebKitHitTestResult* hit_test_result, gpointer user_data) { + auto* self = static_cast(user_data); + + // Disable context menu if setting is enabled + if (self->settings_ && self->settings_->disableContextMenu) { + return TRUE; // Suppress context menu + } + + // Store the context menu and hit test result for native menu display + // We need to ref these because WebKit may release them after this callback returns + if (self->pending_context_menu_ != nullptr) { + g_object_unref(self->pending_context_menu_); + } + self->pending_context_menu_ = context_menu; + g_object_ref(context_menu); + + if (self->pending_hit_test_result_ != nullptr) { + g_object_unref(self->pending_hit_test_result_); + } + self->pending_hit_test_result_ = hit_test_result; + if (hit_test_result != nullptr) { + g_object_ref(hit_test_result); + } + + // Notify Dart side that context menu is being created + if (self->channel_delegate_) { + HitTestResult hitTestResult = HitTestResult::fromWebKitHitTestResult(self->pending_hit_test_result_); + self->channel_delegate_->onCreateContextMenu(hitTestResult); + } + + // Store the current cursor position for the context menu + self->context_menu_x_ = self->cursor_x_; + self->context_menu_y_ = self->cursor_y_; + + // Show the context menu + self->ShowNativeContextMenu(); + + // Return TRUE to suppress WebKit's default context menu handling + return TRUE; +} + +void InAppWebView::OnContextMenuDismissed(WebKitWebView* web_view, gpointer user_data) { + auto* self = static_cast(user_data); + + // Hide our custom context menu popup when WebKit signals dismissal + self->HideContextMenu(); +} + +void InAppWebView::ShowNativeContextMenu() { + // Use cached GTK window for the popup menu + if (gtk_window_ == nullptr) { + errorLog("InAppWebView: Cannot show context menu - no window available"); + return; + } + + // Create context menu popup if needed + if (!context_menu_popup_) { + context_menu_popup_ = std::make_unique(gtk_window_); + + // Set up callbacks + context_menu_popup_->SetItemCallback( + [this](const std::string& id, const std::string& title) { + // Try to execute the action directly using WebKit APIs + int action_id = 0; + try { + action_id = std::stoi(id); + } catch (...) { + action_id = 0; + } + + if (action_id > 0 && webview_ != nullptr) { + // Execute stock actions directly via WebKit API + WebKitContextMenuAction action = static_cast(action_id); + switch (action) { + case WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION: + // No action, used by separator menu items + break; + + // === Link Actions === + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK: + // Open the link in current view + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_link_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_load_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK_IN_NEW_WINDOW: + // Open link in new window - notify Dart side to handle + if (pending_hit_test_result_ != nullptr && channel_delegate_) { + const gchar* uri = webkit_hit_test_result_get_link_uri(pending_hit_test_result_); + if (uri != nullptr) { + // For now, just open in current view (Dart can handle new window logic) + webkit_web_view_load_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_LINK_TO_DISK: + // Download link - trigger download + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_link_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_download_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_COPY_LINK_TO_CLIPBOARD: + // Copy link URI to both system and WebView clipboard + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_link_uri(pending_hit_test_result_); + if (uri != nullptr) { + copyTextToClipboard(uri); + } + } + break; + + // === Image Actions === + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_IMAGE_IN_NEW_WINDOW: + // Open image in new window + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_image_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_load_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_IMAGE_TO_DISK: + // Download image + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_image_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_download_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_TO_CLIPBOARD: + // Copy image URI to both system and WebView clipboard + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_image_uri(pending_hit_test_result_); + if (uri != nullptr) { + copyTextToClipboard(uri); + } + } + break; + + // === Frame Actions === + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_FRAME_IN_NEW_WINDOW: + // Open frame in new window - use GAction fallback + break; + + // === Navigation Actions === + case WEBKIT_CONTEXT_MENU_ACTION_GO_BACK: + webkit_web_view_go_back(webview_); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD: + webkit_web_view_go_forward(webview_); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_STOP: + webkit_web_view_stop_loading(webview_); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_RELOAD: + webkit_web_view_reload(webview_); + break; + + // === Editing Actions === + // These use the clipboard methods that sync WPE WebKit with system clipboard + case WEBKIT_CONTEXT_MENU_ACTION_COPY: + copyToClipboard(); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_CUT: + cutToClipboard(); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_PASTE: + pasteFromClipboard(); + break; + + // === Spelling Actions === + case WEBKIT_CONTEXT_MENU_ACTION_SPELLING_GUESS: + // Spelling suggestion - use GAction fallback + break; + + case WEBKIT_CONTEXT_MENU_ACTION_NO_GUESSES_FOUND: + // No spelling guesses - informational only + break; + + case WEBKIT_CONTEXT_MENU_ACTION_IGNORE_SPELLING: + // Ignore spelling - use GAction fallback + break; + + case WEBKIT_CONTEXT_MENU_ACTION_LEARN_SPELLING: + // Learn spelling - use GAction fallback + break; + + case WEBKIT_CONTEXT_MENU_ACTION_IGNORE_GRAMMAR: + // Ignore grammar - use GAction fallback + break; + + // === Font Actions === + case WEBKIT_CONTEXT_MENU_ACTION_FONT_MENU: + // Font submenu - use GAction fallback + break; + + case WEBKIT_CONTEXT_MENU_ACTION_BOLD: + webkit_web_view_execute_editing_command(webview_, "Bold"); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_ITALIC: + webkit_web_view_execute_editing_command(webview_, "Italic"); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_UNDERLINE: + webkit_web_view_execute_editing_command(webview_, "Underline"); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_OUTLINE: + // Outline - use GAction fallback + break; + + // === Developer Tools === + case WEBKIT_CONTEXT_MENU_ACTION_INSPECT_ELEMENT: + // Inspect element - not available in WPE WebKit without GTK inspector + // Fall through to GAction fallback + break; + + // === Video Actions === + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_VIDEO_IN_NEW_WINDOW: + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_load_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_COPY_VIDEO_LINK_TO_CLIPBOARD: + // Copy video URI to both system and WebView clipboard + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + copyTextToClipboard(uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_VIDEO_TO_DISK: + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_download_uri(webview_, uri); + } + } + break; + + // === Audio Actions === + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_AUDIO_IN_NEW_WINDOW: + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_load_uri(webview_, uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_COPY_AUDIO_LINK_TO_CLIPBOARD: + // Copy audio URI to both system and WebView clipboard + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + copyTextToClipboard(uri); + } + } + break; + + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_AUDIO_TO_DISK: + if (pending_hit_test_result_ != nullptr) { + const gchar* uri = webkit_hit_test_result_get_media_uri(pending_hit_test_result_); + if (uri != nullptr) { + webkit_web_view_download_uri(webview_, uri); + } + } + break; + + // === Media Control Actions === + case WEBKIT_CONTEXT_MENU_ACTION_TOGGLE_MEDIA_CONTROLS: + // Toggle media controls - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.controls !== undefined) {" + " document.activeElement.controls = !document.activeElement.controls;" + "}", + std::nullopt, + nullptr); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_TOGGLE_MEDIA_LOOP: + // Toggle media loop - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.loop !== undefined) {" + " document.activeElement.loop = !document.activeElement.loop;" + "}", + std::nullopt, + nullptr); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_ENTER_VIDEO_FULLSCREEN: + // Enter video fullscreen - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.requestFullscreen) {" + " document.activeElement.requestFullscreen();" + "} else if(document.activeElement && document.activeElement.webkitEnterFullscreen) {" + " document.activeElement.webkitEnterFullscreen();" + "}", + std::nullopt, + nullptr); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_MEDIA_PLAY: + // Play media - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.play) {" + " document.activeElement.play();" + "}", + std::nullopt, + nullptr); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_MEDIA_PAUSE: + // Pause media - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.pause) {" + " document.activeElement.pause();" + "}", + std::nullopt, + nullptr); + break; + + case WEBKIT_CONTEXT_MENU_ACTION_MEDIA_MUTE: + // Toggle mute - use JavaScript + evaluateJavascript( + "if(document.activeElement && document.activeElement.muted !== undefined) {" + " document.activeElement.muted = !document.activeElement.muted;" + "}", + std::nullopt, + nullptr); + break; + + // === Custom Actions === + case WEBKIT_CONTEXT_MENU_ACTION_CUSTOM: + // Custom action - handled by Dart side + break; + + default: + // For any unhandled actions, try the GAction approach as fallback + if (pending_context_menu_ != nullptr) { + GList* items = webkit_context_menu_get_items(pending_context_menu_); + for (GList* l = items; l != nullptr; l = l->next) { + WebKitContextMenuItem* webkit_item = WEBKIT_CONTEXT_MENU_ITEM(l->data); + WebKitContextMenuAction stock_action = + webkit_context_menu_item_get_stock_action(webkit_item); + if (static_cast(stock_action) == action_id) { + GAction* gaction = webkit_context_menu_item_get_gaction(webkit_item); + if (gaction != nullptr && g_action_get_enabled(gaction)) { + g_action_activate(gaction, nullptr); + } + break; + } + } + } + break; + } + } + + // Notify Dart side about the menu item click + if (channel_delegate_) { + channel_delegate_->onContextMenuActionItemClicked(id, title); + } + }); + + context_menu_popup_->SetDismissedCallback([this]() { + // Only notify if the popup was actually visible (prevents duplicate notifications) + if (context_menu_popup_ && context_menu_popup_->IsVisible() && channel_delegate_) { + channel_delegate_->onHideContextMenu(); + } + }); + } + + // Clear and rebuild the menu + context_menu_popup_->Clear(); + + // Get context menu settings + bool hideDefaultItems = false; + if (context_menu_config_ != nullptr) { + hideDefaultItems = context_menu_config_->settings.hideDefaultSystemContextMenuItems; + } + + bool hasItems = false; + + // Add items from the WebKit context menu if available and not hiding default items + if (pending_context_menu_ != nullptr && !hideDefaultItems) { + GList* items = webkit_context_menu_get_items(pending_context_menu_); + for (GList* l = items; l != nullptr; l = l->next) { + WebKitContextMenuItem* webkit_item = WEBKIT_CONTEXT_MENU_ITEM(l->data); + + // Get the stock action to determine the menu item type + WebKitContextMenuAction stock_action = webkit_context_menu_item_get_stock_action(webkit_item); + + // Skip separator and custom actions for now + if (stock_action == WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION) { + context_menu_popup_->AddSeparator(); + continue; + } + + // Get action using GAction API (WPE WebKit uses GAction, not GtkAction) + GAction* gaction = webkit_context_menu_item_get_gaction(webkit_item); + bool enabled = (gaction != nullptr) ? g_action_get_enabled(gaction) : true; + + // Map stock actions to labels + const char* stock_label = nullptr; + switch (stock_action) { + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK: + stock_label = "Open Link"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK_IN_NEW_WINDOW: + stock_label = "Open Link in New Window"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_LINK_TO_DISK: + stock_label = "Download Link"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_COPY_LINK_TO_CLIPBOARD: + stock_label = "Copy Link"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_OPEN_IMAGE_IN_NEW_WINDOW: + stock_label = "Open Image in New Window"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_IMAGE_TO_DISK: + stock_label = "Download Image"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_TO_CLIPBOARD: + stock_label = "Copy Image"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_GO_BACK: + stock_label = "Back"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD: + stock_label = "Forward"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_STOP: + stock_label = "Stop"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_RELOAD: + stock_label = "Reload"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_COPY: + stock_label = "Copy"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_CUT: + stock_label = "Cut"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_PASTE: + stock_label = "Paste"; + break; + case WEBKIT_CONTEXT_MENU_ACTION_INSPECT_ELEMENT: + stock_label = "Inspect Element"; + break; + default: + continue; + } + + if (stock_label != nullptr) { + context_menu_popup_->AddItem(std::to_string(static_cast(stock_action)), stock_label, + enabled); + hasItems = true; + } + } + } + + // Add custom menu items from context_menu_config_ + if (context_menu_config_ != nullptr && !context_menu_config_->menuItems.empty()) { + if (hasItems) { + context_menu_popup_->AddSeparator(); + } + + for (const auto& customItem : context_menu_config_->menuItems) { + if (!customItem.title.empty()) { + context_menu_popup_->AddItem(customItem.getIdAsString(), customItem.title, true); + hasItems = true; + } + } + } + + if (!hasItems) { + return; + } + + // Get screen position of the cursor using the pointer device + // This works reliably on both X11 and Wayland + gint screen_x = 0, screen_y = 0; + + GdkDisplay* gdk_display = gdk_display_get_default(); + if (gdk_display != nullptr) { + GdkSeat* seat = gdk_display_get_default_seat(gdk_display); + if (seat != nullptr) { + GdkDevice* pointer = gdk_seat_get_pointer(seat); + if (pointer != nullptr) { + gdk_device_get_position(pointer, nullptr, &screen_x, &screen_y); + } + } + } + + context_menu_popup_->Show(screen_x, screen_y); +} + +void InAppWebView::HideContextMenu() { + // Check if context menu is actually visible before proceeding + bool wasVisible = context_menu_popup_ && context_menu_popup_->IsVisible(); + + // Hide the context menu popup + if (context_menu_popup_) { + context_menu_popup_->Hide(); + } + + // Clean up pending menu references + if (pending_context_menu_ != nullptr) { + g_object_unref(pending_context_menu_); + pending_context_menu_ = nullptr; + } + + if (pending_hit_test_result_ != nullptr) { + g_object_unref(pending_hit_test_result_); + pending_hit_test_result_ = nullptr; + } + + // Notify Dart side only if context menu was actually visible + if (wasVisible && channel_delegate_) { + channel_delegate_->onHideContextMenu(); + } +} + +// === Color Picker for === +// WPE WebKit doesn't have the run-color-chooser signal that WebKitGTK has, +// so we implement color input support via JavaScript interception and a native GTK3 dialog. + +// Helper function to parse hex color string to GdkRGBA +static bool ParseHexColor(const std::string& hexColor, GdkRGBA* rgba) { + if (hexColor.empty() || hexColor[0] != '#') { + return false; + } + + std::string hex = hexColor.substr(1); // Remove '#' + + unsigned int r = 0, g = 0, b = 0, a = 255; + + if (hex.length() == 6) { + // #RRGGBB + if (sscanf(hex.c_str(), "%02x%02x%02x", &r, &g, &b) != 3) { + return false; + } + } else if (hex.length() == 8) { + // #RRGGBBAA + if (sscanf(hex.c_str(), "%02x%02x%02x%02x", &r, &g, &b, &a) != 4) { + return false; + } + } else if (hex.length() == 3) { + // #RGB (shorthand) + if (sscanf(hex.c_str(), "%1x%1x%1x", &r, &g, &b) != 3) { + return false; + } + r = r * 17; // Expand 0-15 to 0-255 + g = g * 17; + b = b * 17; + } else { + return false; + } + + rgba->red = r / 255.0; + rgba->green = g / 255.0; + rgba->blue = b / 255.0; + rgba->alpha = a / 255.0; + + return true; +} + +// Helper function to convert GdkRGBA to hex color string +static std::string RgbaToHexColor(const GdkRGBA* rgba, bool includeAlpha) { + int r = static_cast(rgba->red * 255.0 + 0.5); + int g = static_cast(rgba->green * 255.0 + 0.5); + int b = static_cast(rgba->blue * 255.0 + 0.5); + int a = static_cast(rgba->alpha * 255.0 + 0.5); + + // Clamp values + r = std::max(0, std::min(255, r)); + g = std::max(0, std::min(255, g)); + b = std::max(0, std::min(255, b)); + a = std::max(0, std::min(255, a)); + + char hexColor[10]; + if (includeAlpha && a != 255) { + snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X%02X", r, g, b, a); + } else { + snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", r, g, b); + } + + return std::string(hexColor); +} + +// Static callback for non-blocking color dialog response +static void OnColorDialogResponse(GtkDialog* dialog, gint response_id, gpointer user_data) { + auto* self = static_cast(user_data); + + // Capture and clear reply before resolving (to prevent use after cleanup) + WebKitScriptMessageReply* reply = self->pending_color_reply_; + self->pending_color_reply_ = nullptr; + + if (reply == nullptr) { + // No reply object - just cleanup + gtk_widget_destroy(GTK_WIDGET(dialog)); + g_object_unref(dialog); + self->active_color_dialog_ = nullptr; + self->color_dialog_show_time_ = 0; + return; + } + + if (response_id == GTK_RESPONSE_OK) { + // User selected a color + GdkRGBA selectedRgba; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &selectedRgba); + + std::string hexColor = RgbaToHexColor(&selectedRgba, self->active_color_alpha_enabled_); + // Resolve the Promise with the selected color via webkit reply + self->ResolveInternalHandlerWithReply(reply, "\"" + hexColor + "\""); + } else { + // User cancelled or closed the dialog - resolve with null + self->ResolveInternalHandlerWithReply(reply, "null"); + } + + // Cleanup + gtk_widget_destroy(GTK_WIDGET(dialog)); + g_object_unref(dialog); // Release our extra reference + self->active_color_dialog_ = nullptr; + self->color_dialog_show_time_ = 0; +} + +void InAppWebView::ShowColorPicker(const std::string& initialColor, int x, int y, + const std::vector& predefinedColors, + bool alphaEnabled, + const std::string& colorSpace) { + (void)x; // Position not used for non-modal dialog + (void)y; + (void)colorSpace; // GTK3 color chooser doesn't support color spaces + + // Close any existing color dialog first + if (active_color_dialog_ != nullptr) { + gtk_widget_destroy(active_color_dialog_); + g_object_unref(active_color_dialog_); + active_color_dialog_ = nullptr; + } + + // Store the initial color for cancel restoration + pending_color_input_value_ = initialColor; + active_color_alpha_enabled_ = alphaEnabled; + + // Create the color chooser dialog with parent window + GtkWidget* dialog = gtk_color_chooser_dialog_new("Select Color", gtk_window_); + active_color_dialog_ = dialog; + + // Set dialog as transient for parent (floats above but doesn't block) + if (gtk_window_) { + gtk_window_set_transient_for(GTK_WINDOW(dialog), gtk_window_); + } + + // Take an extra reference to prevent premature destruction + g_object_ref(dialog); + + GtkColorChooser* chooser = GTK_COLOR_CHOOSER(dialog); + + // Set the initial color + GdkRGBA initialRgba = {0.0, 0.0, 0.0, 1.0}; // Default to black + if (ParseHexColor(initialColor, &initialRgba)) { + gtk_color_chooser_set_rgba(chooser, &initialRgba); + } + + // Enable/disable alpha channel + gtk_color_chooser_set_use_alpha(chooser, alphaEnabled ? TRUE : FALSE); + + // Add predefined colors if provided + if (!predefinedColors.empty()) { + std::vector rgbaColors; + rgbaColors.reserve(predefinedColors.size()); + + for (const auto& hexColor : predefinedColors) { + GdkRGBA rgba; + if (ParseHexColor(hexColor, &rgba)) { + rgbaColors.push_back(rgba); + } + } + + if (!rgbaColors.empty()) { + gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, + static_cast(rgbaColors.size()), + static_cast(rgbaColors.size()), + rgbaColors.data()); + } + } + + // Connect to response signal for non-blocking behavior + g_signal_connect(dialog, "response", G_CALLBACK(OnColorDialogResponse), this); + + // Show the dialog and all its children (non-blocking) + gtk_widget_show_all(dialog); + + // Record show time to prevent immediate close by pointer events + color_dialog_show_time_ = g_get_monotonic_time(); +} + +void InAppWebView::HideColorPicker() { + // Don't hide the color dialog when called from HideAllPopups during focus changes. + // The color dialog is a separate GTK window that should remain open until the user + // explicitly closes it (OK/Cancel). The dialog's response handler will clean up properly. + // This is intentionally a no-op - the dialog manages its own lifecycle. +} + +void InAppWebView::HideFileChooser() { + if (active_file_dialog_ != nullptr) { + // Don't close the dialog if it was just shown (within 200ms) + // This prevents the click that triggered the file chooser from immediately closing it + int64_t now = g_get_monotonic_time(); + int64_t elapsed_us = now - file_dialog_show_time_; + if (elapsed_us < 200000) { + return; + } + + // Cancel the WebKit request if we have context + if (file_chooser_context_ != nullptr) { + auto* context = static_cast(file_chooser_context_); + // Disconnect the signal handler to prevent callback from being called + if (context->response_handler_id != 0) { + g_signal_handler_disconnect(active_file_dialog_, context->response_handler_id); + context->response_handler_id = 0; + } + // Cancel the WebKit request so future file chooser signals work + webkit_file_chooser_request_cancel(context->request); + delete context; + file_chooser_context_ = nullptr; + } + + gtk_widget_destroy(active_file_dialog_); + g_object_unref(active_file_dialog_); + active_file_dialog_ = nullptr; + file_dialog_show_time_ = 0; + } +} + +void InAppWebView::HideOptionMenu() { + if (option_menu_popup_ && option_menu_popup_->IsVisible()) { + option_menu_popup_->Hide(); + } +} + +void InAppWebView::HideAllPopups() { + // Hide all custom popups - add new popup types here as they are implemented + HideContextMenu(); + HideColorPicker(); + HideFileChooser(); + HideOptionMenu(); + HideDatePicker(); +} + +void InAppWebView::ResolveInternalHandlerWithReply(WebKitScriptMessageReply* reply, const std::string& jsonResult) { + if (reply == nullptr) { + debugLog("ResolveInternalHandlerWithReply: reply is NULL, cannot respond"); + return; + } + + // Create a JSCContext to build the reply value + JSCContext* context = jsc_context_new(); + if (context == nullptr) { + webkit_script_message_reply_return_error_message(reply, "Failed to create JSC context"); + webkit_script_message_reply_unref(reply); + return; + } + + // Parse the JSON result and create a JSCValue + JSCValue* replyValue = nullptr; + + if (jsonResult == "null" || jsonResult.empty()) { + replyValue = jsc_value_new_null(context); + } else if (jsonResult[0] == '"') { + // It's a quoted string - evaluate it to get the actual string value + replyValue = jsc_context_evaluate(context, jsonResult.c_str(), -1); + } else { + // Parse as JSON + std::string parseScript = "(" + jsonResult + ")"; + replyValue = jsc_context_evaluate(context, parseScript.c_str(), -1); + } + + if (replyValue == nullptr) { + // Fallback: return the raw string + replyValue = jsc_value_new_string(context, jsonResult.c_str()); + } + + // Send the reply back to JavaScript + webkit_script_message_reply_return_value(reply, replyValue); + + // Cleanup + g_object_unref(replyValue); + g_object_unref(context); + webkit_script_message_reply_unref(reply); +} + +void InAppWebView::RejectInternalHandlerWithReply(WebKitScriptMessageReply* reply, const std::string& errorMessage) { + if (reply == nullptr) { + return; + } + webkit_script_message_reply_return_error_message(reply, errorMessage.c_str()); + webkit_script_message_reply_unref(reply); +} + +// === Date/Time Picker Methods === +// WPE WebKit doesn't have date picker support, so we handle = 1 && *month <= 12) { + *day = 1; + return true; + } + + // Try YYYY-Www format (week) + int week = 0; + if (sscanf(isoDate.c_str(), "%d-W%d", year, &week) == 2) { + // Set to first day of the week + *month = 1; + *day = 1 + (week - 1) * 7; // Approximate + return true; + } + + return false; +} + +// Context struct for date dialog callback +struct DateDialogContext { + InAppWebView* webview; + std::string inputType; + GtkWidget* calendar; // GtkCalendar widget (may be null for time-only) + GtkWidget* hourSpin; // Hour spinner (for time/datetime-local) + GtkWidget* minuteSpin; // Minute spinner (for time/datetime-local) + GtkWidget* dialog; // Reference to the dialog for validation + // Min/max constraints (parsed) + int minYear, minMonth, minDay, minHour, minMinute; + int maxYear, maxMonth, maxDay, maxHour, maxMinute; + bool hasMin, hasMax; +}; + +// Helper to compare dates +static int CompareDates(int y1, int m1, int d1, int y2, int m2, int d2) { + if (y1 != y2) return y1 - y2; + if (m1 != m2) return m1 - m2; + return d1 - d2; +} + +// Helper to compare times (for future use with time validation) +[[maybe_unused]] +static int CompareTimes(int h1, int m1, int h2, int m2) { + if (h1 != h2) return h1 - h2; + return m1 - m2; +} + +// Callback for calendar day selection to validate against min/max +static void OnCalendarDaySelected(GtkCalendar* calendar, gpointer user_data) { + auto* ctx = static_cast(user_data); + + guint year, month, day; + gtk_calendar_get_date(calendar, &year, &month, &day); + month += 1; // GtkCalendar months are 0-based + + bool valid = true; + + // Check min constraint + if (ctx->hasMin) { + if (CompareDates(static_cast(year), static_cast(month), static_cast(day), + ctx->minYear, ctx->minMonth, ctx->minDay) < 0) { + valid = false; + } + } + + // Check max constraint + if (ctx->hasMax) { + if (CompareDates(static_cast(year), static_cast(month), static_cast(day), + ctx->maxYear, ctx->maxMonth, ctx->maxDay) > 0) { + valid = false; + } + } + + // Enable/disable OK button based on validity + if (ctx->dialog) { + gtk_dialog_set_response_sensitive(GTK_DIALOG(ctx->dialog), GTK_RESPONSE_OK, valid ? TRUE : FALSE); + } +} + +// Static callback for date dialog response +static void OnDateDialogResponse(GtkDialog* dialog, gint response_id, gpointer user_data) { + auto* ctx = static_cast(user_data); + InAppWebView* self = ctx->webview; + + // Capture and clear reply before resolving (to prevent use after cleanup) + WebKitScriptMessageReply* reply = self->pending_date_reply_; + self->pending_date_reply_ = nullptr; + + // Helper lambda for cleanup + auto cleanup = [&]() { + delete ctx; + gtk_widget_destroy(GTK_WIDGET(dialog)); + g_object_unref(dialog); + self->active_date_dialog_ = nullptr; + self->date_dialog_show_time_ = 0; + }; + + if (reply == nullptr) { + // No reply object - just cleanup + cleanup(); + return; + } + + if (response_id == GTK_RESPONSE_OK) { + std::string result; + + if (ctx->inputType == "time") { + // Time only + int hour = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctx->hourSpin)); + int minute = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctx->minuteSpin)); + char buf[16]; + snprintf(buf, sizeof(buf), "%02d:%02d", hour, minute); + result = buf; + } else if (ctx->calendar != nullptr) { + // Get date from calendar + guint year, month, day; + gtk_calendar_get_date(GTK_CALENDAR(ctx->calendar), &year, &month, &day); + month += 1; // GtkCalendar months are 0-based + + if (ctx->inputType == "date") { + char buf[16]; + snprintf(buf, sizeof(buf), "%04d-%02d-%02d", year, month, day); + result = buf; + } else if (ctx->inputType == "datetime-local") { + int hour = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctx->hourSpin)); + int minute = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctx->minuteSpin)); + char buf[32]; + snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d", year, month, day, hour, minute); + result = buf; + } else if (ctx->inputType == "month") { + char buf[16]; + snprintf(buf, sizeof(buf), "%04d-%02d", year, month); + result = buf; + } else if (ctx->inputType == "week") { + // Calculate ISO week number + GDateTime* dt = g_date_time_new_local(year, month, day, 0, 0, 0); + if (dt) { + int isoYear = g_date_time_get_year(dt); + int weekNum = g_date_time_get_week_of_year(dt); + g_date_time_unref(dt); + char buf[16]; + snprintf(buf, sizeof(buf), "%04d-W%02d", isoYear, weekNum); + result = buf; + } + } + } + + if (!result.empty()) { + // Resolve the Promise with the selected value via webkit reply + self->ResolveInternalHandlerWithReply(reply, "\"" + result + "\""); + } else { + // No valid result - resolve with null + self->ResolveInternalHandlerWithReply(reply, "null"); + } + } else { + // User cancelled - resolve with null + self->ResolveInternalHandlerWithReply(reply, "null"); + } + + // Cleanup + cleanup(); +} + +void InAppWebView::ShowDatePicker(const std::string& inputType, const std::string& value, + const std::string& min, const std::string& max, + const std::string& step, int x, int y) { + (void)x; // Position not used - GTK handles dialog placement + (void)y; + + // Close any existing date dialog + if (active_date_dialog_ != nullptr) { + gtk_widget_destroy(active_date_dialog_); + g_object_unref(active_date_dialog_); + active_date_dialog_ = nullptr; + } + + pending_date_input_value_ = value; + pending_date_input_type_ = inputType; + pending_date_input_min_ = min; + pending_date_input_max_ = max; + + // Determine dialog title + std::string title; + if (inputType == "date") title = "Select Date"; + else if (inputType == "datetime-local") title = "Select Date and Time"; + else if (inputType == "time") title = "Select Time"; + else if (inputType == "month") title = "Select Month"; + else if (inputType == "week") title = "Select Week"; + else title = "Select Date"; + + // Create a dialog with the parent window + GtkWidget* dialog = gtk_dialog_new_with_buttons( + title.c_str(), + gtk_window_, + GTK_DIALOG_DESTROY_WITH_PARENT, + "_Cancel", GTK_RESPONSE_CANCEL, + "_OK", GTK_RESPONSE_OK, + nullptr); + + active_date_dialog_ = dialog; + g_object_ref(dialog); + + // Set dialog as transient for parent (floats above but doesn't block) + if (gtk_window_) { + gtk_window_set_transient_for(GTK_WINDOW(dialog), gtk_window_); + } + + // Make the window non-resizable + gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); + + GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_set_border_width(GTK_CONTAINER(content_area), 10); + + // Create context for callback + auto* ctx = new DateDialogContext(); + ctx->webview = this; + ctx->inputType = inputType; + ctx->calendar = nullptr; + ctx->hourSpin = nullptr; + ctx->minuteSpin = nullptr; + ctx->dialog = dialog; + + // Parse min/max constraints + ctx->hasMin = ParseIsoDate(min, &ctx->minYear, &ctx->minMonth, &ctx->minDay, &ctx->minHour, &ctx->minMinute); + ctx->hasMax = ParseIsoDate(max, &ctx->maxYear, &ctx->maxMonth, &ctx->maxDay, &ctx->maxHour, &ctx->maxMinute); + + // Parse step value for time inputs (step is in seconds) + int stepMinutes = 1; // Default minute step + if (!step.empty()) { + int stepSeconds = std::atoi(step.c_str()); + if (stepSeconds >= 60) { + stepMinutes = stepSeconds / 60; + } + } + + // Parse initial value + int initialYear = 2024, initialMonth = 1, initialDay = 1; + int initialHour = 12, initialMinute = 0; + ParseIsoDate(value, &initialYear, &initialMonth, &initialDay, &initialHour, &initialMinute); + + // Add calendar for date-related types + if (inputType == "date" || inputType == "datetime-local" || + inputType == "month" || inputType == "week") { + GtkWidget* calendar = gtk_calendar_new(); + ctx->calendar = calendar; + + // Set initial date (GtkCalendar months are 0-based) + gtk_calendar_select_month(GTK_CALENDAR(calendar), initialMonth - 1, initialYear); + gtk_calendar_select_day(GTK_CALENDAR(calendar), initialDay); + + // For month picker, hide day selection styling (user can still click but day isn't important) + if (inputType == "month") { + gtk_calendar_set_display_options(GTK_CALENDAR(calendar), + static_cast( + GTK_CALENDAR_SHOW_HEADING | GTK_CALENDAR_SHOW_DAY_NAMES)); + } + + // For week picker, show week numbers + if (inputType == "week") { + gtk_calendar_set_display_options(GTK_CALENDAR(calendar), + static_cast( + GTK_CALENDAR_SHOW_HEADING | GTK_CALENDAR_SHOW_DAY_NAMES | + GTK_CALENDAR_SHOW_WEEK_NUMBERS)); + } + + gtk_box_pack_start(GTK_BOX(content_area), calendar, TRUE, TRUE, 5); + + // Connect day-selected signal to validate against min/max + g_signal_connect(calendar, "day-selected", G_CALLBACK(OnCalendarDaySelected), ctx); + + // Add constraint info label if min or max is set + if (ctx->hasMin || ctx->hasMax) { + std::string constraintText; + if (ctx->hasMin && ctx->hasMax) { + char buf[64]; + snprintf(buf, sizeof(buf), "Range: %04d-%02d-%02d to %04d-%02d-%02d", + ctx->minYear, ctx->minMonth, ctx->minDay, + ctx->maxYear, ctx->maxMonth, ctx->maxDay); + constraintText = buf; + } else if (ctx->hasMin) { + char buf[32]; + snprintf(buf, sizeof(buf), "Min: %04d-%02d-%02d", ctx->minYear, ctx->minMonth, ctx->minDay); + constraintText = buf; + } else { + char buf[32]; + snprintf(buf, sizeof(buf), "Max: %04d-%02d-%02d", ctx->maxYear, ctx->maxMonth, ctx->maxDay); + constraintText = buf; + } + GtkWidget* constraintLabel = gtk_label_new(constraintText.c_str()); + gtk_widget_set_opacity(constraintLabel, 0.7); + gtk_box_pack_start(GTK_BOX(content_area), constraintLabel, FALSE, FALSE, 2); + } + + // For date type with no time, double-click on day to confirm quickly + if (inputType == "date") { + g_signal_connect(calendar, "day-selected-double-click", + G_CALLBACK(+[](GtkCalendar*, gpointer user_data) { + auto* dialog = GTK_DIALOG(user_data); + gtk_dialog_response(dialog, GTK_RESPONSE_OK); + }), dialog); + } + } + + // Add time spinners for time-related types + if (inputType == "time" || inputType == "datetime-local") { + GtkWidget* timeBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + gtk_widget_set_halign(timeBox, GTK_ALIGN_CENTER); + + // Hour spinner + GtkAdjustment* hourAdj = gtk_adjustment_new(initialHour, 0, 23, 1, 1, 0); + GtkWidget* hourSpin = gtk_spin_button_new(hourAdj, 1, 0); + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(hourSpin), TRUE); + gtk_entry_set_width_chars(GTK_ENTRY(hourSpin), 2); + ctx->hourSpin = hourSpin; + + // Separator + GtkWidget* separator = gtk_label_new(":"); + + // Minute spinner (use step if provided) + GtkAdjustment* minuteAdj = gtk_adjustment_new(initialMinute, 0, 59, stepMinutes, stepMinutes * 5, 0); + GtkWidget* minuteSpin = gtk_spin_button_new(minuteAdj, 1, 0); + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(minuteSpin), TRUE); + gtk_entry_set_width_chars(GTK_ENTRY(minuteSpin), 2); + gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(minuteSpin), stepMinutes > 1 ? TRUE : FALSE); + ctx->minuteSpin = minuteSpin; + + gtk_box_pack_start(GTK_BOX(timeBox), gtk_label_new("Time:"), FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(timeBox), hourSpin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(timeBox), separator, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(timeBox), minuteSpin, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(content_area), timeBox, FALSE, FALSE, 10); + + // Add time constraint info for time-only input + if (inputType == "time" && (ctx->hasMin || ctx->hasMax)) { + std::string timeConstraint; + if (ctx->hasMin && ctx->hasMax) { + char buf[32]; + snprintf(buf, sizeof(buf), "Range: %02d:%02d to %02d:%02d", + ctx->minHour, ctx->minMinute, ctx->maxHour, ctx->maxMinute); + timeConstraint = buf; + } else if (ctx->hasMin) { + char buf[16]; + snprintf(buf, sizeof(buf), "Min: %02d:%02d", ctx->minHour, ctx->minMinute); + timeConstraint = buf; + } else { + char buf[16]; + snprintf(buf, sizeof(buf), "Max: %02d:%02d", ctx->maxHour, ctx->maxMinute); + timeConstraint = buf; + } + GtkWidget* timeConstraintLabel = gtk_label_new(timeConstraint.c_str()); + gtk_widget_set_opacity(timeConstraintLabel, 0.7); + gtk_box_pack_start(GTK_BOX(content_area), timeConstraintLabel, FALSE, FALSE, 2); + } + } + + // Connect response signal + g_signal_connect(dialog, "response", G_CALLBACK(OnDateDialogResponse), ctx); + + // Handle focus-out to auto-close (like a dropdown) + g_signal_connect(dialog, "focus-out-event", + G_CALLBACK(+[](GtkWidget* widget, GdkEventFocus*, gpointer) -> gboolean { + // Don't auto-close - let user interact freely + // They can click Cancel or click outside to dismiss + (void)widget; + return FALSE; // Don't consume the event + }), nullptr); + + // Show the dialog and all its children + gtk_widget_show_all(dialog); + + date_dialog_show_time_ = g_get_monotonic_time(); +} + +void InAppWebView::HideDatePicker() { + if (active_date_dialog_ != nullptr) { + int64_t now = g_get_monotonic_time(); + int64_t elapsed_us = now - date_dialog_show_time_; + if (elapsed_us < 200000) { + return; // Don't close if just shown + } + + // Capture and clear reply before resolving + WebKitScriptMessageReply* reply = pending_date_reply_; + pending_date_reply_ = nullptr; + + // Resolve the pending Promise with null when hiding the picker + if (reply != nullptr) { + ResolveInternalHandlerWithReply(reply, "null"); + } + + gtk_widget_destroy(active_date_dialog_); + g_object_unref(active_date_dialog_); + active_date_dialog_ = nullptr; + date_dialog_show_time_ = 0; + } +} + +// === Clipboard Operations === +// WPE WebKit runs offscreen and doesn't share clipboard with the system by default. +// These methods sync WebKit's internal clipboard with the GTK/system clipboard. + +void InAppWebView::getSelectedText(std::function&)> callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(std::nullopt); + return; + } + + evaluateJavascript( + "window.getSelection().toString()", + std::nullopt, + [callback](const std::optional& result) { + if (!result.has_value() || result->empty() || *result == "null") { + callback(std::nullopt); + return; + } + + std::string text = *result; + + // Parse JSON string result using nlohmann/json + try { + auto parsed = json::parse(text); + if (parsed.is_string()) { + text = parsed.get(); + } + } catch (const json::exception&) { + // If parsing fails, try manual unquoting for simple cases + if (text.size() >= 2 && text.front() == '"' && text.back() == '"') { + text = text.substr(1, text.size() - 2); + } + } + + if (text.empty()) { + callback(std::nullopt); + } else { + callback(text); + } + }); +} + +void InAppWebView::isSecureContext(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(false); + return; + } + + evaluateJavascript("window.isSecureContext", + std::nullopt, + [callback](const std::optional& result) { + bool isSecure = false; + if (result.has_value() && *result == "true") { + isSecure = true; + } + callback(isSecure); + }); +} + +// === Media Playback Control === + +void InAppWebView::pauseAllMediaPlayback() { + if (webview_ == nullptr) return; + + // Pause all audio and video elements in the page + const char* script = R"( + (function() { + var mediaElements = document.querySelectorAll('audio, video'); + mediaElements.forEach(function(el) { + if (!el.paused) { + el.pause(); + } + }); + })(); + )"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::setAllMediaPlaybackSuspended(bool suspended) { + if (webview_ == nullptr) return; + + // Suspend or resume all media elements + // When suspended=true, pause all and mark with a data attribute + // When suspended=false, resume only those that were playing before + std::string script; + if (suspended) { + script = R"( + (function() { + var mediaElements = document.querySelectorAll('audio, video'); + mediaElements.forEach(function(el) { + if (!el.paused) { + el.dataset.wasPlaying = 'true'; + el.pause(); + } else { + el.dataset.wasPlaying = 'false'; + } + }); + })(); + )"; + } else { + script = R"( + (function() { + var mediaElements = document.querySelectorAll('audio, video'); + mediaElements.forEach(function(el) { + if (el.dataset.wasPlaying === 'true') { + el.play().catch(function() {}); + delete el.dataset.wasPlaying; + } + }); + })(); + )"; + } + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::closeAllMediaPresentations() { + if (webview_ == nullptr) return; + + // Exit fullscreen if any media is in fullscreen, and exit picture-in-picture + const char* script = R"( + (function() { + // Exit fullscreen + if (document.fullscreenElement) { + document.exitFullscreen().catch(function() {}); + } + // Exit picture-in-picture + if (document.pictureInPictureElement) { + document.exitPictureInPicture().catch(function() {}); + } + // Also pause all media + var mediaElements = document.querySelectorAll('audio, video'); + mediaElements.forEach(function(el) { + if (!el.paused) { + el.pause(); + } + }); + })(); + )"; + evaluateJavascript(script, std::nullopt, nullptr); +} + +void InAppWebView::requestMediaPlaybackState(std::function callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(0); // NONE + return; + } + + // Use native WPE WebKit API for PLAYING detection (webkit_web_view_is_playing_audio) + // This is more accurate than JavaScript as it's tracked at the browser level + // Returns: 0 = NONE, 1 = PAUSED, 2 = SUSPENDED, 3 = PLAYING + + gboolean isPlayingAudio = webkit_web_view_is_playing_audio(webview_); + + if (isPlayingAudio) { + // Native API confirms audio is playing - return PLAYING immediately + callback(3); // PLAYING + return; + } + + // If not playing, use JavaScript to determine if we have media elements + // and whether they are paused normally or suspended via our API + const char* script = R"( + (function() { + var mediaElements = document.querySelectorAll('audio, video'); + if (mediaElements.length === 0) { + return 0; // NONE - no media elements + } + var hasSuspended = false; + var hasPaused = false; + mediaElements.forEach(function(el) { + if (el.dataset.wasPlaying === 'true') { + hasSuspended = true; + } else if (el.paused || el.ended) { + hasPaused = true; + } + }); + if (hasSuspended) return 2; // SUSPENDED (paused via setAllMediaPlaybackSuspended) + if (hasPaused) return 1; // PAUSED (normal pause) + return 0; // NONE + })(); + )"; + + evaluateJavascript(script, std::nullopt, [callback](const std::optional& result) { + int state = 0; // NONE + if (result.has_value()) { + try { + state = std::stoi(*result); + } catch (...) { + state = 0; + } + } + callback(state); + }); +} + +// === Media Capture State (Camera and Microphone) === + +int InAppWebView::getCameraCaptureState() const { + if (webview_ == nullptr) return 0; // NONE + + // WPE WebKit returns WebKitMediaCaptureState enum: + // WEBKIT_MEDIA_CAPTURE_STATE_NONE = 0 + // WEBKIT_MEDIA_CAPTURE_STATE_ACTIVE = 1 + // WEBKIT_MEDIA_CAPTURE_STATE_MUTED = 2 + WebKitMediaCaptureState state = webkit_web_view_get_camera_capture_state(webview_); + return static_cast(state); +} + +void InAppWebView::setCameraCaptureState(int state) { + if (webview_ == nullptr) return; + + // state: 0 = NONE, 1 = ACTIVE, 2 = MUTED + // Note: Once state is set to NONE, it cannot be changed back (per WPE docs) + // The page can request capture again using mediaDevices API + WebKitMediaCaptureState captureState = static_cast(state); + webkit_web_view_set_camera_capture_state(webview_, captureState); +} + +int InAppWebView::getMicrophoneCaptureState() const { + if (webview_ == nullptr) return 0; // NONE + + WebKitMediaCaptureState state = webkit_web_view_get_microphone_capture_state(webview_); + return static_cast(state); +} + +void InAppWebView::setMicrophoneCaptureState(int state) { + if (webview_ == nullptr) return; + + WebKitMediaCaptureState captureState = static_cast(state); + webkit_web_view_set_microphone_capture_state(webview_, captureState); +} + +// === Theme Color === + +std::optional InAppWebView::getMetaThemeColor() const { + if (webview_ == nullptr) return std::nullopt; + + WebKitColor color; + gboolean hasColor = webkit_web_view_get_theme_color(webview_, &color); + + if (!hasColor) { + return std::nullopt; + } + + // Convert WebKitColor (RGBA in 0.0-1.0 range) to hex string format #RRGGBBAA + // Note: WebKitColor has red, green, blue, alpha as gdouble (0.0-1.0) + int r = static_cast(color.red * 255.0 + 0.5); + int g = static_cast(color.green * 255.0 + 0.5); + int b = static_cast(color.blue * 255.0 + 0.5); + int a = static_cast(color.alpha * 255.0 + 0.5); + + // Clamp values to 0-255 + r = std::max(0, std::min(255, r)); + g = std::max(0, std::min(255, g)); + b = std::max(0, std::min(255, b)); + a = std::max(0, std::min(255, a)); + + char hexColor[10]; + if (a == 255) { + snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", r, g, b); + } else { + snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X%02X", r, g, b, a); + } + + return std::string(hexColor); +} + +// === Audio State (Playing and Mute) === + +bool InAppWebView::isPlayingAudio() const { + if (webview_ == nullptr) return false; + + // WPE WebKit 2.8+ + return webkit_web_view_is_playing_audio(webview_) == TRUE; +} + +bool InAppWebView::isMuted() const { + if (webview_ == nullptr) return false; + + // WPE WebKit 2.30+ + return webkit_web_view_get_is_muted(webview_) == TRUE; +} + +void InAppWebView::setMuted(bool muted) { + if (webview_ == nullptr) return; + + // WPE WebKit 2.30+ + webkit_web_view_set_is_muted(webview_, muted ? TRUE : FALSE); +} + +// === Web Process Control === + +void InAppWebView::terminateWebProcess() { + if (webview_ == nullptr) return; + + // WPE WebKit 2.34+ + // Terminates the web process. The web-process-terminated signal will be emitted + // with WEBKIT_WEB_PROCESS_TERMINATED_BY_API as the reason. + webkit_web_view_terminate_web_process(webview_); +} + +// === Focus Control === + +bool InAppWebView::clearFocus() { + if (webview_ == nullptr) return false; + + // Remove focused state from WPE backend + setFocused(false); + + return true; +} + +bool InAppWebView::requestFocus() { + if (webview_ == nullptr) return false; + + // Add focused state to WPE backend + setFocused(true); + + return true; +} + +// === Web Archive === + +void InAppWebView::saveWebArchive(const std::string& filePath, bool autoname, + std::function&)> callback) { + if (webview_ == nullptr || callback == nullptr) { + if (callback) callback(std::nullopt); + return; + } + + std::string finalPath = filePath; + + // If autoname is true, generate a filename based on the current URL + if (autoname) { + const gchar* uri = webkit_web_view_get_uri(webview_); + if (uri == nullptr) { + callback(std::nullopt); + return; + } + + // Clean the URL to create a valid filename + std::string urlStr(uri); + // Replace invalid filename characters + std::string filename; + for (char c : urlStr) { + if (c == '/' || c == '\\' || c == ':' || c == '*' || + c == '?' || c == '"' || c == '<' || c == '>' || c == '|') { + filename += '_'; + } else { + filename += c; + } + } + + // Limit filename length + if (filename.length() > 200) { + filename = filename.substr(0, 200); + } + + // Append .mht extension (WPE WebKit saves as MHTML) + finalPath = filePath + "/" + filename + ".mht"; + } + + // Create GFile for the destination + GFile* file = g_file_new_for_path(finalPath.c_str()); + if (file == nullptr) { + callback(std::nullopt); + return; + } + + // Create callback data structure + struct SaveCallbackData { + std::function&)> callback; + std::string filePath; + InAppWebView* self; + }; + + auto* data = new SaveCallbackData{callback, finalPath, this}; + + // Use webkit_web_view_save_to_file with MHTML format + webkit_web_view_save_to_file( + webview_, + file, + WEBKIT_SAVE_MODE_MHTML, + nullptr, // GCancellable + [](GObject* source_object, GAsyncResult* result, gpointer user_data) { + auto* callbackData = static_cast(user_data); + WebKitWebView* webView = WEBKIT_WEB_VIEW(source_object); + + GError* error = nullptr; + gboolean success = webkit_web_view_save_to_file_finish(webView, result, &error); + + if (success) { + callbackData->callback(callbackData->filePath); + } else { + if (error != nullptr) { + debugLog("InAppWebView::saveWebArchive failed: " + std::string(error->message)); + g_error_free(error); + } + callbackData->callback(std::nullopt); + } + + delete callbackData; + }, + data + ); + + g_object_unref(file); +} + +void InAppWebView::copyToClipboard() { + if (webview_ == nullptr) return; + + getSelectedText([this](const std::optional& text) { + if (text.has_value() && !text->empty()) { + copyTextToClipboard(*text); + } + }); + + // Also execute WebKit's copy command for internal state + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_COPY); +} + +void InAppWebView::cutToClipboard() { + if (webview_ == nullptr) return; + + getSelectedText([this](const std::optional& text) { + if (text.has_value() && !text->empty()) { + copyTextToClipboard(*text); + } + }); + + // Execute WebKit's cut command to remove the selection + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_CUT); +} + +void InAppWebView::pasteFromClipboard() { + if (webview_ == nullptr) return; + + // Check if JavaScript is disabled - use WebKit's paste command as fallback + bool jsEnabled = settings_ ? settings_->javaScriptEnabled : true; + + // Get text from system clipboard + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gchar* text = gtk_clipboard_wait_for_text(clipboard); + + if (text != nullptr && strlen(text) > 0) { + if (jsEnabled) { + // Use JavaScript to insert text + std::string escaped; + escaped.reserve(strlen(text) * 2); + for (const char* p = text; *p; ++p) { + switch (*p) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + default: escaped += *p; break; + } + } + g_free(text); + + // Insert text at current cursor position using execCommand + std::string js = "document.execCommand('insertText', false, \"" + escaped + "\")"; + evaluateJavascript(js, std::nullopt, nullptr); + } else { + // JavaScript disabled - use WebKit's paste command + g_free(text); + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_PASTE); + } + } else { + if (text) g_free(text); + // If system clipboard is empty, try WebKit's paste (may have its own content) + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_PASTE); + } +} + +void InAppWebView::copyTextToClipboard(const std::string& text) { + if (text.empty()) return; + + // 1. Copy to GTK/system clipboard + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clipboard, text.c_str(), -1); + gtk_clipboard_store(clipboard); + + // 2. Copy to WebView's internal clipboard using the Clipboard API + // This ensures the text is available for paste within the webview + if (webview_ != nullptr) { + // Escape text for JavaScript + std::string escaped; + escaped.reserve(text.size() * 2); + for (char c : text) { + switch (c) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + case '`': escaped += "\\`"; break; + case '$': escaped += "\\$"; break; + default: escaped += c; break; + } + } + + // Use the modern Clipboard API with fallback + std::string js = R"( + (async function() { + const text = ")" + escaped + R"("; + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(text); + } + } catch (e) { + // Fallback: create temporary textarea and use execCommand + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + } + })(); + )"; + evaluateJavascript(js, std::nullopt, nullptr); + } +} + +void InAppWebView::pasteAsPlainText() { + if (webview_ == nullptr) return; + + // Check if JavaScript is disabled - use WebKit's paste as plain text command as fallback + bool jsEnabled = settings_ ? settings_->javaScriptEnabled : true; + + // Get text from system clipboard + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gchar* text = gtk_clipboard_wait_for_text(clipboard); + + if (text != nullptr && strlen(text) > 0) { + if (jsEnabled) { + // Use JavaScript to insert text (already plain text from gtk_clipboard_wait_for_text) + std::string escaped; + escaped.reserve(strlen(text) * 2); + for (const char* p = text; *p; ++p) { + switch (*p) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + default: escaped += *p; break; + } + } + g_free(text); + + // Insert text at current cursor position using execCommand + std::string js = "document.execCommand('insertText', false, \"" + escaped + "\")"; + evaluateJavascript(js, std::nullopt, nullptr); + } else { + // JavaScript disabled - use WebKit's paste as plain text command + g_free(text); + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_PASTE_AS_PLAIN_TEXT); + } + } else { + if (text) g_free(text); + // If system clipboard is empty, try WebKit's paste as plain text + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_PASTE_AS_PLAIN_TEXT); + } +} + +void InAppWebView::selectAll() { + if (webview_ == nullptr) return; + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_SELECT_ALL); +} + +void InAppWebView::undo() { + if (webview_ == nullptr) return; + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_UNDO); +} + +void InAppWebView::redo() { + if (webview_ == nullptr) return; + webkit_web_view_execute_editing_command(webview_, WEBKIT_EDITING_COMMAND_REDO); +} + +void InAppWebView::insertImage(const std::string& imageUri) { + if (webview_ == nullptr || imageUri.empty()) return; + webkit_web_view_execute_editing_command_with_argument( + webview_, WEBKIT_EDITING_COMMAND_INSERT_IMAGE, imageUri.c_str()); +} + +void InAppWebView::createLink(const std::string& linkUri) { + if (webview_ == nullptr || linkUri.empty()) return; + webkit_web_view_execute_editing_command_with_argument( + webview_, WEBKIT_EDITING_COMMAND_CREATE_LINK, linkUri.c_str()); +} + +gboolean InAppWebView::OnEnterFullscreen(WebKitWebView* web_view, gpointer user_data) { + auto* self = static_cast(user_data); + self->is_fullscreen_ = true; + + // Notify WPE backend that we entered fullscreen +#ifdef HAVE_WPE_BACKEND_LEGACY + if (self->wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_did_enter_fullscreen(self->wpe_backend_); + } +#endif + + if (self->channel_delegate_) { + self->channel_delegate_->onEnterFullscreen(); + } + return TRUE; // We handled the fullscreen request +} + +gboolean InAppWebView::OnLeaveFullscreen(WebKitWebView* web_view, gpointer user_data) { + auto* self = static_cast(user_data); + self->is_fullscreen_ = false; + + // Notify WPE backend that we exited fullscreen +#ifdef HAVE_WPE_BACKEND_LEGACY + if (self->wpe_backend_ != nullptr) { + wpe_view_backend_dispatch_did_exit_fullscreen(self->wpe_backend_); + } +#endif + + if (self->channel_delegate_) { + self->channel_delegate_->onExitFullscreen(); + } + return TRUE; // We handled the fullscreen exit +} + +void InAppWebView::OnMouseTargetChanged(WebKitWebView* web_view, + WebKitHitTestResult* hit_test_result, guint modifiers, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Store the hit test result for getHitTestResult() + // Release previous result and ref new one (if not null) + if (self->last_hit_test_result_ != nullptr) { + g_object_unref(self->last_hit_test_result_); + self->last_hit_test_result_ = nullptr; + } + if (hit_test_result != nullptr) { + self->last_hit_test_result_ = hit_test_result; + g_object_ref(hit_test_result); + } + + // Determine cursor based on hit test result + // Use CSS cursor names which gdk_cursor_new_from_name() supports + std::string cursor_name = "default"; + + if (hit_test_result == nullptr) { + // No hit test result, use default cursor + } else if (webkit_hit_test_result_context_is_link(hit_test_result)) { + cursor_name = "pointer"; // Hand cursor for links + } else if (webkit_hit_test_result_context_is_editable(hit_test_result)) { + cursor_name = "text"; // Text cursor for editable content + } else if (webkit_hit_test_result_context_is_selection(hit_test_result)) { + cursor_name = "text"; // Text cursor for selection + } else if (webkit_hit_test_result_context_is_image(hit_test_result)) { + // Check if image is also a link + if (webkit_hit_test_result_context_is_link(hit_test_result)) { + cursor_name = "pointer"; + } + } else if (webkit_hit_test_result_context_is_media(hit_test_result)) { + cursor_name = "default"; + } else if (webkit_hit_test_result_context_is_scrollbar(hit_test_result)) { + cursor_name = "default"; + } + + // Only emit if cursor changed + if (cursor_name != self->last_cursor_name_) { + self->last_cursor_name_ = cursor_name; + if (self->on_cursor_changed_) { + self->on_cursor_changed_(cursor_name); + } + } +} + +void InAppWebView::OnWebProcessTerminated(WebKitWebView* web_view, + WebKitWebProcessTerminationReason reason, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Determine if this was a crash or a kill + // - WEBKIT_WEB_PROCESS_CRASHED: The web process crashed -> didCrash = true + // - WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT: Killed by system due to memory -> didCrash = false + // - WEBKIT_WEB_PROCESS_TERMINATED_BY_API: Terminated via API call -> didCrash = false + bool didCrash = (reason == WEBKIT_WEB_PROCESS_CRASHED); + + // Log the termination with detailed information + const char* reason_str = "unknown"; + switch (reason) { + case WEBKIT_WEB_PROCESS_CRASHED: + reason_str = "CRASHED"; + break; + case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT: + reason_str = "EXCEEDED_MEMORY_LIMIT"; + break; + case WEBKIT_WEB_PROCESS_TERMINATED_BY_API: + reason_str = "TERMINATED_BY_API"; + break; + default: + break; + } + + g_warning("InAppWebView[%ld]: WebProcess terminated (reason=%s, didCrash=%s)", + self->id_, reason_str, didCrash ? "true" : "false"); + +#ifdef HAVE_WPE_BACKEND_LEGACY + // IMPORTANT: When WebProcess crashes (especially from "Failed to bind wl_compositor"), + // the WPE FDO connection is broken. We should NOT call any WPE FDO functions here + // as they may cause additional errors or hangs. Simply null out the pointer. + // + // The exported_image_ was being used by the crashed WebProcess, and its underlying + // Wayland resources are now invalid. Calling wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image + // on a broken connection can cause further issues. + { + std::lock_guard lock(self->exported_image_mutex_); + if (self->exported_image_ != nullptr) { + g_message("InAppWebView[%ld]: Nulling stale EGL image %p after WebProcess termination (not releasing via WPE FDO)", + self->id_, (void*)self->exported_image_); + // Don't call WPE FDO release - the connection is broken + self->exported_image_ = nullptr; + } + } +#endif // HAVE_WPE_BACKEND_LEGACY + + if (self->channel_delegate_) { + self->channel_delegate_->onRenderProcessGone(didCrash); + } +} + +// Static callback for non-blocking file chooser dialog response +static void OnFileChooserDialogResponse(GtkDialog* dialog, gint response_id, gpointer user_data) { + auto* context = static_cast(user_data); + + // Check if this dialog is still the active one (prevents double-cleanup) + if (context->webview && context->webview->active_file_dialog_ != GTK_WIDGET(dialog)) { + // Dialog was already cleaned up by HideFileChooser(), just delete context + delete context; + return; + } + + GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog); + + if (response_id == GTK_RESPONSE_ACCEPT) { + if (context->selectMultiple) { + // Get multiple files + GSList* files = gtk_file_chooser_get_filenames(chooser); + if (files != nullptr) { + std::vector filePaths; + for (GSList* item = files; item != nullptr; item = item->next) { + gchar* filename = static_cast(item->data); + if (filename != nullptr) { + // Convert to file:// URI + gchar* uri = g_filename_to_uri(filename, nullptr, nullptr); + if (uri != nullptr) { + filePaths.push_back(uri); + g_free(uri); + } + g_free(filename); + } + } + g_slist_free(files); + + // Convert to gchar** format for WebKit + std::vector uris; + for (const auto& path : filePaths) { + uris.push_back(path.c_str()); + } + uris.push_back(nullptr); // NULL-terminated + webkit_file_chooser_request_select_files(context->request, uris.data()); + } else { + webkit_file_chooser_request_cancel(context->request); + } + } else { + // Get single file + gchar* filename = gtk_file_chooser_get_filename(chooser); + if (filename != nullptr) { + gchar* uri = g_filename_to_uri(filename, nullptr, nullptr); + if (uri != nullptr) { + const gchar* uris[] = {uri, nullptr}; + webkit_file_chooser_request_select_files(context->request, uris); + g_free(uri); + } else { + webkit_file_chooser_request_cancel(context->request); + } + g_free(filename); + } else { + webkit_file_chooser_request_cancel(context->request); + } + } + } else { + // User cancelled (Cancel button, X button, or Escape key) + webkit_file_chooser_request_cancel(context->request); + } + + // Clear tracking in webview + if (context->webview) { + context->webview->active_file_dialog_ = nullptr; + context->webview->file_dialog_show_time_ = 0; + context->webview->file_chooser_context_ = nullptr; + } + + gtk_widget_destroy(GTK_WIDGET(dialog)); + g_object_unref(dialog); // Release our extra reference + delete context; +} + +// Helper function to show native GTK file chooser dialog (non-blocking) +static void ShowNativeFileChooser(InAppWebView* webview, + WebKitFileChooserRequest* request, + bool selectMultiple, + const std::vector& mimeTypes, + GtkWindow* parentWindow) { + // Close any existing file dialog first + if (webview && webview->active_file_dialog_ != nullptr) { + // Clean up old context if it exists + if (webview->file_chooser_context_ != nullptr) { + auto* old_context = static_cast(webview->file_chooser_context_); + if (old_context->response_handler_id != 0) { + g_signal_handler_disconnect(webview->active_file_dialog_, old_context->response_handler_id); + } + webkit_file_chooser_request_cancel(old_context->request); + delete old_context; + webview->file_chooser_context_ = nullptr; + } + gtk_widget_destroy(webview->active_file_dialog_); + g_object_unref(webview->active_file_dialog_); + webview->active_file_dialog_ = nullptr; + } + + // Create the file chooser dialog with parent window + GtkWidget* dialog = gtk_file_chooser_dialog_new( + "Select File", + parentWindow, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + nullptr); + + // Track the dialog in webview + if (webview) { + webview->active_file_dialog_ = dialog; + } + + // Set dialog as transient for parent (floats above but doesn't block) + if (parentWindow) { + gtk_window_set_transient_for(GTK_WINDOW(dialog), parentWindow); + } + + // Take an extra reference to prevent premature destruction + g_object_ref(dialog); + + GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog); + + // Enable multiple selection if requested + gtk_file_chooser_set_select_multiple(chooser, selectMultiple ? TRUE : FALSE); + + // Add file filters based on MIME types + if (!mimeTypes.empty()) { + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "Allowed files"); + for (const auto& mimeType : mimeTypes) { + gtk_file_filter_add_mime_type(filter, mimeType.c_str()); + } + gtk_file_chooser_add_filter(chooser, filter); + + // Also add an "All files" filter + GtkFileFilter* allFilter = gtk_file_filter_new(); + gtk_file_filter_set_name(allFilter, "All files"); + gtk_file_filter_add_pattern(allFilter, "*"); + gtk_file_chooser_add_filter(chooser, allFilter); + } + + // Create context for the callback + auto* context = new FileChooserContext(request, selectMultiple, webview); + + // Store context pointer in webview for cleanup in HideFileChooser + if (webview) { + webview->file_chooser_context_ = context; + } + + // Connect to response signal for non-blocking behavior + context->response_handler_id = g_signal_connect(dialog, "response", + G_CALLBACK(OnFileChooserDialogResponse), context); + + // Show the dialog and all its children (non-blocking) + gtk_widget_show_all(dialog); + + // Record show time to prevent immediate close by pointer events + if (webview) { + webview->file_dialog_show_time_ = g_get_monotonic_time(); + } +} + +gboolean InAppWebView::OnRunFileChooser(WebKitWebView* web_view, + WebKitFileChooserRequest* request, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Get file chooser properties + gboolean select_multiple = webkit_file_chooser_request_get_select_multiple(request); + const gchar* const* mime_types = webkit_file_chooser_request_get_mime_types(request); + + // Build accept types list + std::vector acceptTypes; + if (mime_types) { + for (int i = 0; mime_types[i] != nullptr; i++) { + acceptTypes.push_back(mime_types[i]); + } + } + + // If no channel delegate, show native dialog directly + if (!self->channel_delegate_) { + ShowNativeFileChooser(self, request, select_multiple, acceptTypes, self->gtk_window_); + return TRUE; + } + + // Keep request alive during async callback + g_object_ref(request); + + // Determine mode: OPEN or OPEN_MULTIPLE (WPE doesn't have folder/save modes in this API) + int mode = select_multiple ? 1 : 0; // 0 = OPEN, 1 = OPEN_MULTIPLE + + // Capture variables for the lambda + bool selectMultipleCopy = select_multiple; + std::vector acceptTypesCopy = acceptTypes; + GtkWindow* parentWindow = self->gtk_window_; + + self->channel_delegate_->onShowFileChooser( + mode, + acceptTypes, + false, // isCaptureEnabled - not exposed by WPE API + std::nullopt, // title + std::nullopt, // filenameHint + [self, request, selectMultipleCopy, acceptTypesCopy, parentWindow](ShowFileChooserResponse response) { + if (response.handledByClient) { + // Client handled it - use the file paths from Dart + if (response.filePaths.has_value() && !response.filePaths->empty()) { + // Convert to gchar** format + std::vector files; + for (const auto& path : *response.filePaths) { + files.push_back(path.c_str()); + } + files.push_back(nullptr); // NULL-terminated + webkit_file_chooser_request_select_files(request, files.data()); + } else { + // Client handled but no files selected (cancelled) + webkit_file_chooser_request_cancel(request); + } + } else { + // Client didn't handle it - show native GTK file chooser + ShowNativeFileChooser(self, request, selectMultipleCopy, acceptTypesCopy, parentWindow); + } + g_object_unref(request); + }); + + return TRUE; // We handled it +} + +// === Option Menu (HTML support in WPE) + // Show the native color picker popup with optional predefined colors and alpha support + void ShowColorPicker(const std::string& initialColor, int x, int y, + const std::vector& predefinedColors = {}, + bool alphaEnabled = false, + const std::string& colorSpace = "limited-srgb"); + // Hide and cleanup any visible color picker + void HideColorPicker(); + // Hide and cleanup any visible file chooser dialog + void HideFileChooser(); + // Hide and cleanup any visible option menu (HTML support in WPE) + // Show the native date/time picker dialog + void ShowDatePicker(const std::string& inputType, const std::string& value, + const std::string& min, const std::string& max, + const std::string& step, int x, int y); + // Hide and cleanup any visible date picker + void HideDatePicker(); + + // Resolve an internal handler's Promise with a JSON result via WebKitScriptMessageReply + // Used by color/date picker dialogs to send the result back to JavaScript (works for iframes) + void ResolveInternalHandlerWithReply(WebKitScriptMessageReply* reply, const std::string& jsonResult); + + // JavaScript bridge handler using with_reply API (enables iframe support) + // Returns true if handled, false otherwise + bool handleScriptMessageWithReply(const std::string& body, WebKitScriptMessageReply* reply); + + // Reject an internal handler's Promise with an error message via WebKitScriptMessageReply + void RejectInternalHandlerWithReply(WebKitScriptMessageReply* reply, const std::string& errorMessage); + + // Hide all custom popups (context menu, color picker, file chooser, option menu, etc.) + // Use this when the webview state changes (resize, scroll, load, focus loss, etc.) + void HideAllPopups(); + + // Clipboard operations (syncs WPE WebKit clipboard with system clipboard) + void copyToClipboard(); + void cutToClipboard(); + void pasteFromClipboard(); + void pasteAsPlainText(); + void copyTextToClipboard(const std::string& text); // Copy arbitrary text to both clipboards + void getSelectedText(std::function&)> callback); + void isSecureContext(std::function callback); + + // Media playback control + void pauseAllMediaPlayback(); + void setAllMediaPlaybackSuspended(bool suspended); + void closeAllMediaPresentations(); + void requestMediaPlaybackState(std::function callback); + + // Media capture state (camera and microphone) + int getCameraCaptureState() const; + void setCameraCaptureState(int state); + int getMicrophoneCaptureState() const; + void setMicrophoneCaptureState(int state); + + // Theme color (from tag) + std::optional getMetaThemeColor() const; + + // Audio state (mute and playback) + bool isPlayingAudio() const; + bool isMuted() const; + void setMuted(bool muted); + + // Web process control + void terminateWebProcess(); + + // Focus control + bool clearFocus(); + bool requestFocus(); + + // Web archive (save page to file) + void saveWebArchive(const std::string& filePath, bool autoname, + std::function&)> callback); + + // Editing commands (WebKit editing commands) + void selectAll(); + void undo(); + void redo(); + void insertImage(const std::string& imageUri); + void createLink(const std::string& linkUri); + + // Check if WPE WebKit is available on the system + static bool IsWpeWebKitAvailable(); + +#ifdef HAVE_WPE_PLATFORM + // Check if DMA-BUF rendering should be used + // Returns true if DMA-BUF rendering is expected to work, false if SHM should be used + // Note: Environment detection is done at plugin registration via utils/software_rendering.h + static bool PreflightDmaBufSupport(); +#endif + + // === Multi-Window Support === + + // Set the window ID for this webview (used in window.open scenarios) + void setWindowId(int64_t windowId) { window_id_ = windowId; } + + // Get the window ID (null if not set) + std::optional getWindowId() const { return window_id_; } + + // Initialize the window ID JavaScript variable in the webview + // This injects JS to set window._flutter_inappwebview_windowId + void initializeWindowIdJS(); + + // Get the GTK window (for focus restoration after popup dialogs) + GtkWindow* getGtkWindow() const { return gtk_window_; } + + // Get the FlView (for focus restoration after popup dialogs) + FlView* getFlView() const { return fl_view_; } + + private: + PluginInstance* plugin_ = nullptr; // Plugin instance for accessing managers + FlPluginRegistrar* registrar_ = nullptr; + FlBinaryMessenger* messenger_ = nullptr; // Cached messenger from constructor + GtkWindow* gtk_window_ = nullptr; // Cached GTK window for context menu display + FlView* fl_view_ = nullptr; // Cached FlView for focus restoration + InAppWebViewManager* manager_ = nullptr; // Manager reference for multi-window support + int64_t id_ = 0; + int64_t channel_id_ = -1; + std::string string_channel_id_; // String-based channel ID for headless webviews + + // Settings + std::shared_ptr settings_; + + // Context menu configuration + std::shared_ptr context_menu_config_; + + // WPE WebKit view + WebKitWebView* webview_ = nullptr; + +#ifdef HAVE_WPE_PLATFORM + // === WPEPlatform API members (modern) === + WPEDisplay* wpe_display_ = nullptr; // Owned headless display + WPEView* wpe_view_ = nullptr; // From webkit_web_view_get_wpe_view, not owned + WPEToplevel* wpe_toplevel_ = nullptr; // From wpe_view_get_toplevel, not owned + + // Buffer rendering for WPEPlatform + WPEBuffer* current_buffer_ = nullptr; // Current frame buffer (borrowed, not owned) + void* current_egl_image_ = nullptr; // EGL image created from current buffer + uint32_t current_buffer_width_ = 0; // Width of current buffer + uint32_t current_buffer_height_ = 0; // Height of current buffer + gulong buffer_rendered_handler_ = 0; // Signal handler ID for buffer-rendered + gulong scale_changed_handler_ = 0; // Signal handler ID for notify::scale-factor + mutable std::mutex wpe_buffer_mutex_; // Mutex for thread-safe buffer access +#endif + +#ifdef HAVE_WPE_BACKEND_LEGACY + // === WPEBackend-FDO API members (legacy) === + WebKitWebViewBackend* backend_ = nullptr; + struct wpe_view_backend* wpe_backend_ = nullptr; + + // WPE FDO exportable (for DMA-BUF buffer export) + struct wpe_view_backend_exportable_fdo* exportable_ = nullptr; + + // Current EGL image from WPE (for zero-copy GPU texture sharing). + ::wpe_fdo_egl_exported_image* exported_image_ = nullptr; + + // Mutex for protecting exported_image_ access from multiple threads + mutable std::mutex exported_image_mutex_; + + // Flag to indicate the WebProcess has crashed and EGL resources are invalid + // This prevents using stale EGL images after a crash + std::atomic web_process_crashed_{false}; +#endif + + // EGL context for reading back pixels (both APIs) + void* egl_display_ = nullptr; // EGLDisplay + void* egl_context_ = nullptr; // EGLContext for readback + unsigned int fbo_ = 0; // Framebuffer object for EGL image binding + unsigned int readback_texture_ = 0; // Texture for EGL image + + // Triple buffering for pixel data (fallback when DMA-BUF not available) + static constexpr size_t kNumBuffers = 3; + struct PixelBuffer { + std::vector data; + size_t width = 0; + size_t height = 0; + }; + std::array pixel_buffers_; + std::atomic write_buffer_index_{0}; + std::atomic read_buffer_index_{1}; + mutable std::mutex buffer_swap_mutex_; + + // Flag to skip pixel readback when using zero-copy EGL texture mode + // When true, OnExportDmaBuf won't call ReadPixelsFromEglImage + bool skip_pixel_readback_ = false; + + // View dimensions + int width_ = 800; + int height_ = 600; + double scale_factor_ = 1.0; + + // Channel delegate + std::unique_ptr channel_delegate_; + + // User content controller + std::unique_ptr user_content_controller_; + + // Find interaction controller + std::unique_ptr findInteractionController_; + + // Content blocker handler for Safari-style content blocking rules + std::unique_ptr content_blocker_handler_; + + // Web message channels (for WebMessageChannel support) + std::map> web_message_channels_; + + // Web message listeners (for WebMessageListener support - federated plugin pattern) + // Key is jsObjectName, value is the WebMessageListener + std::map> web_message_listeners_; + + // Initial user scripts from params + std::vector> initial_user_scripts_; + + // JavaScript bridge secret for security + std::string js_bridge_secret_; + + // Window ID for multi-window support + std::optional window_id_; + + // Flag to track if javaScriptBridgeEnabled + bool java_script_bridge_enabled_ = true; + + // Pending policy decisions + std::map pending_policy_decisions_; + int64_t next_decision_id_ = 0; + + // Pending script dialogs + std::map pending_script_dialogs_; + int64_t next_dialog_id_ = 0; + + // Pending permission requests + std::map pending_permission_requests_; + int64_t next_permission_id_ = 0; + + // Pending authentication requests + std::map pending_auth_requests_; + int64_t next_auth_id_ = 0; + + // Pending custom scheme requests (for async handling) + std::map pending_custom_scheme_requests_; + + // Frame available callback + std::function on_frame_available_; + + // Cursor change callback + std::function on_cursor_changed_; + std::string last_cursor_name_ = "default"; + + // Progress change callback (for InAppBrowser) + std::function on_progress_changed_; + + // Navigation state change callback (for InAppBrowser back/forward buttons) + std::function on_navigation_state_changed_; + + // InAppBrowser delegate (when embedded in an InAppBrowser) + // This allows WebViewChannelDelegate to forward browser-specific method calls + InAppBrowser* inAppBrowserDelegate_ = nullptr; + + // Last hit test result from mouse-target-changed signal + // Used by getHitTestResult() to return the current element under the cursor + WebKitHitTestResult* last_hit_test_result_ = nullptr; + + // Disposing flag to prevent callbacks during destruction + std::atomic is_disposing_{false}; + + // Mouse state + double cursor_x_ = 0; + double cursor_y_ = 0; + uint32_t button_state_ = 0; + uint32_t current_modifiers_ = 0; // Current keyboard modifiers (shift, ctrl, alt, meta) + + // Scroll multiplier + double scroll_multiplier_ = 1.0; + + // Progress tracking + double last_progress_ = 0.0; + + // Media capture state tracking (for onCameraCaptureStateChanged/onMicrophoneCaptureStateChanged) + int last_camera_capture_state_ = 0; // WebKitMediaCaptureState: NONE=0, ACTIVE=1, MUTED=2 + int last_microphone_capture_state_ = 0; // WebKitMediaCaptureState: NONE=0, ACTIVE=1, MUTED=2 + + // Fullscreen state (for DOM fullscreen requests) + bool is_fullscreen_ = false; + bool waiting_fullscreen_notify_ = false; + + // Activity/focus state + bool is_focused_ = true; + bool is_visible_ = true; + + // Target refresh rate (0 = default) + uint32_t target_refresh_rate_ = 0; + + // Monitor change tracking for refresh rate updates + gulong monitors_changed_handler_id_ = 0; + gulong configure_event_handler_id_ = 0; + + // Download signal handler ID + gulong download_started_handler_id_ = 0; + + // Context menu state + std::unique_ptr context_menu_popup_; + WebKitContextMenu* pending_context_menu_ = nullptr; + WebKitHitTestResult* pending_hit_test_result_ = nullptr; + double context_menu_x_ = 0; // Mouse position when context menu was requested + double context_menu_y_ = 0; + double texture_offset_x_ = 0; // Texture offset within the Flutter window + double texture_offset_y_ = 0; + + // Option menu state (for HTML support in WPE) + // Public because accessed from C-style GTK callback + std::string pending_color_input_value_; // Current color from the input + GtkWidget* active_color_dialog_ = nullptr; // Active color picker dialog (non-blocking) + bool active_color_alpha_enabled_ = false; // Alpha enabled for active dialog + int64_t color_dialog_show_time_ = 0; // Time when dialog was shown (to prevent immediate close) + WebKitScriptMessageReply* pending_color_reply_ = nullptr; // WebKit reply for Promise resolution + + // Date picker state (for support) + // Public because accessed from C-style GTK callback + GtkWidget* active_file_dialog_ = nullptr; // Active file chooser dialog (non-blocking) + int64_t file_dialog_show_time_ = 0; // Time when dialog was shown (to prevent immediate close) + void* file_chooser_context_ = nullptr; // Opaque pointer to FileChooserContext (for cleanup) + + // Option menu state (for HTML elements and handle them + * natively via the JavaScript bridge. + * + * This implementation uses Firefox/Chrome-style behavior: + * - No DOM modifications (no wrapper elements or icon overlays) + * - The entire color input is clickable (like native browsers) + * - Uses document-level event delegation for efficient handling + * - Supports keyboard navigation (Enter/Space) + * - Works with dynamically added inputs automatically + */ +class ColorInputJS { + public: + inline static const std::string COLOR_INPUT_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_COLOR_INPUT_JS_PLUGIN_SCRIPT"; + + /** + * JavaScript source code for color input interception. + * This code intercepts clicks on color inputs and calls the native handler. + * Supports the 'list' attribute for predefined color swatches. + * + * This script uses Firefox/Chrome-style behavior: + * 1. Injects minimal CSS (cursor:pointer, disabled styling only) + * 2. Uses document-level event delegation to intercept all color input clicks + * 3. Intercepts the ENTIRE color input element (not just an icon area) + * 4. Prevents default browser behavior and opens native GTK color picker + * 5. Supports keyboard accessibility (Enter/Space keys) + * 6. No DOM modifications - keeps the native color swatch appearance + */ + static std::string COLOR_INPUT_JS_SOURCE() { + return R"JS( +(function() { + // Avoid re-registering + if (window._flutterInAppWebViewColorInputInit) return; + window._flutterInAppWebViewColorInputInit = true; + + // CSS to style color inputs like Firefox/Chrome (colored box, not text input) + // WPE WebKit renders color inputs as text fields, so we need to style them properly + var style = document.createElement('style'); + style.textContent = [ + 'input[type="color"] {', + ' -webkit-appearance: none;', + ' appearance: none;', + ' width: 44px;', + ' height: 28px;', + ' padding: 2px;', + ' border: 1px solid #767676;', + ' border-radius: 4px;', + ' cursor: pointer;', + ' background-color: transparent;', + '}', + 'input[type="color"]::-webkit-color-swatch-wrapper {', + ' padding: 0;', + '}', + 'input[type="color"]::-webkit-color-swatch {', + ' border: none;', + ' border-radius: 2px;', + '}', + 'input[type="color"]:disabled {', + ' cursor: not-allowed;', + ' opacity: 0.5;', + '}', + 'input[type="color"]:focus {', + ' outline: 2px solid #4A90D9;', + ' outline-offset: 1px;', + '}' + ].join('\n'); + (document.head || document.documentElement).appendChild(style); + + // Calculate relative luminance of a color (0-1, where 0 is darkest, 1 is lightest) + function getLuminance(hexColor) { + var hex = hexColor.replace('#', ''); + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + var r = parseInt(hex.substr(0, 2), 16) / 255; + var g = parseInt(hex.substr(2, 2), 16) / 255; + var b = parseInt(hex.substr(4, 2), 16) / 255; + // sRGB luminance formula + r = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); + g = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); + b = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; + } + + // Function to apply color as background with contrasting text color + function applyColorBackground(input) { + if (input.attributes.type && input.attributes.type.value === 'color') { + var color = input.value || '#000000'; + input.style.backgroundColor = color; + // Set text color based on luminance for maximum contrast + var luminance = getLuminance(color); + input.style.color = luminance > 0.5 ? '#000000' : '#FFFFFF'; + } + } + + // Update color background when value changes + document.addEventListener('input', function(event) { + if (event.target.tagName === 'INPUT' && event.target.attributes.type && event.target.attributes.type.value === 'color') { + applyColorBackground(event.target); + } + }, true); + + // Apply to existing color inputs + document.querySelectorAll('input[type="color"]').forEach(applyColorBackground); + + // Also apply when new color inputs are added + var colorObserver = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName === 'INPUT' && node.attributes.type && node.attributes.type.value === 'color') { + applyColorBackground(node); + } else if (node.querySelectorAll) { + node.querySelectorAll('input[type="color"]').forEach(applyColorBackground); + } + } + }); + }); + }); + if (document.body) { + colorObserver.observe(document.body, { childList: true, subtree: true }); + } else { + document.addEventListener('DOMContentLoaded', function() { + document.querySelectorAll('input[type="color"]').forEach(applyColorBackground); + colorObserver.observe(document.body, { childList: true, subtree: true }); + }); + } + + // Function to get element's absolute position + function getElementRect(element) { + var rect = element.getBoundingClientRect(); + return { + x: Math.round(rect.left + window.scrollX), + y: Math.round(rect.top + window.scrollY), + width: Math.round(rect.width), + height: Math.round(rect.height) + }; + } + + // Function to get predefined colors from datalist (list attribute) + function getPredefinedColors(input) { + var colors = []; + var listId = input.getAttribute('list'); + if (listId) { + var datalist = document.getElementById(listId); + if (datalist) { + var options = datalist.querySelectorAll('option'); + for (var i = 0; i < options.length; i++) { + var color = options[i].value || options[i].textContent; + if (color && /^#[0-9A-Fa-f]{6}$/.test(color)) { + colors.push(color.toUpperCase()); + } + } + } + } + return colors; + } + + // Open color picker for an input + function openColorPicker(input) { + // Get element rect for positioning + var elemRect = getElementRect(input); + + // Get current color value (default is #000000) + var currentColor = input.value || '#000000'; + + // Get predefined colors from list attribute + var predefinedColors = getPredefinedColors(input); + + // Detect alpha attribute (HTML boolean attribute) + var alphaEnabled = input.hasAttribute('alpha'); + + // Detect colorspace attribute (limited-srgb or display-p3) + var colorSpace = input.getAttribute('colorspace') || 'limited-srgb'; + + // Use the unified callHandler API through the JavaScript bridge + try { + window.)JS" + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + R"JS(.callHandler('_onColorInputClicked', { + currentColor: currentColor, + elemRect: elemRect, + predefinedColors: predefinedColors, + alphaEnabled: alphaEnabled, + colorSpace: colorSpace + }).then(function(result) { + // Check if the result is a valid color (non-null, non-undefined) + if (result != null && result !== undefined) { + input.value = result; + // Update the background color and text color for contrast + input.style.backgroundColor = result; + var luminance = getLuminance(result); + input.style.color = luminance > 0.5 ? '#000000' : '#FFFFFF'; + // Dispatch input and change events to notify the page + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + } + // If result is null/undefined, user cancelled - do nothing + }).catch(function(e) { + console.error('Color picker error:', e); + }); + } catch(e) { + console.error('Failed to open color picker:', e); + } + } + + // Intercept ALL clicks on color inputs using event delegation + // Use capture phase to intercept before the browser's default handler + document.addEventListener('click', function(event) { + var target = event.target; + + // Check if this is a color input + if (target.tagName === 'INPUT' && target.attributes.type && target.attributes.type.value === 'color') { + // Skip if disabled + if (target.disabled) return; + + // Prevent default browser behavior (WPE's basic color UI) + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + + // Open our native color picker + openColorPicker(target); + } + }, true); // true = capture phase + + // Also intercept mousedown to prevent any focus/selection issues + document.addEventListener('mousedown', function(event) { + var target = event.target; + if (target.tagName === 'INPUT' && target.attributes.type && target.attributes.type.value === 'color' && !target.disabled) { + event.preventDefault(); + } + }, true); + + // Intercept Enter/Space key on focused color inputs for keyboard accessibility + document.addEventListener('keydown', function(event) { + var target = event.target; + if (target.tagName === 'INPUT' && target.attributes.type && target.attributes.type.value === 'color' && !target.disabled) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + event.stopPropagation(); + openColorPicker(target); + } + } + }, true); +})(); +)JS"; + } + + /** + * Creates a PluginScript for color input interception. + */ + static std::unique_ptr COLOR_INPUT_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly) { + return std::make_unique( + COLOR_INPUT_JS_PLUGIN_SCRIPT_GROUP_NAME, COLOR_INPUT_JS_SOURCE(), + UserScriptInjectionTime::atDocumentEnd, // Inject after DOM is ready + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + false, // requiredInAllContentWorlds + std::vector{} // messageHandlerNames - uses existing bridge + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_COLOR_INPUT_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/console_log_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/console_log_js.h new file mode 100644 index 00000000..f1167e99 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/console_log_js.h @@ -0,0 +1,95 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONSOLE_LOG_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONSOLE_LOG_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for capturing console.log, console.error, etc. + * + * This script intercepts console methods and forwards them to the native + * side via the JavaScript bridge. This approach is used because WebKit + * (both GTK and WPE) doesn't have a direct "console-message" signal. + */ +class ConsoleLogJS { + public: + inline static const std::string CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT"; + + /** + * JavaScript source code for console message interception. + * This code wraps console.log, console.error, console.warn, console.info, + * and console.debug to send messages to native code. + * + * Must match iOS/macOS implementation - uses JavaScript bridge callHandler. + */ + static std::string CONSOLE_LOG_JS_SOURCE() { + // Match iOS ConsoleLogJS.swift implementation exactly + return R"JS( +(function(console) { + + function _callHandler(logLevel, args) { + var message = ''; + for (var i in args) { + try { + message += message === '' ? args[i] : ' ' + args[i]; + } catch(_) {} + } + try { + window.)JS" + + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + R"JS(.callHandler('onConsoleMessage', {'level': logLevel, 'message': message}); + } catch(_) {} + } + + var oldLogs = { + 'consoleLog': console.log, + 'consoleDebug': console.debug, + 'consoleError': console.error, + 'consoleInfo': console.info, + 'consoleWarn': console.warn + }; + + for (var k in oldLogs) { + (function(oldLog) { + var logLevel = oldLog.replace('console', '').toLowerCase(); + console[logLevel] = function() { + oldLogs[oldLog].apply(null, arguments); + _callHandler(logLevel, arguments); + } + })(k); + } +})(window.console); +)JS"; + } + + /** + * Creates a PluginScript for console log interception. + * + * Note: This plugin is only for main frame. Using it on non-main frames + * could cause issues such as https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738 + */ + static std::unique_ptr CONSOLE_LOG_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules) { + return std::make_unique( + CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, CONSOLE_LOG_JS_SOURCE(), + UserScriptInjectionTime::atDocumentStart, + true, // forMainFrameOnly + allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CONSOLE_LOG_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/cursor_detection_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/cursor_detection_js.h new file mode 100644 index 00000000..de5ca655 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/cursor_detection_js.h @@ -0,0 +1,269 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CURSOR_DETECTION_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CURSOR_DETECTION_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for detecting cursor style changes in the WebView. + * + * This script monitors cursor style changes as the user moves the mouse, + * and reports them to native code. WPE WebKit doesn't expose cursor information + * directly like GTK does, so we use JavaScript to detect CSS cursor properties. + */ +class CursorDetectionJS { + public: + inline static const std::string CURSOR_DETECTION_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_CURSOR_DETECTION_JS_PLUGIN_SCRIPT"; + + /** + * JavaScript source code for cursor detection. + * + * The script detects the effective cursor style by: + * 1. First checking the computed CSS cursor value + * 2. If "auto", analyzing the element to determine appropriate cursor: + * - Links ( with href) -> pointer + * - Editable elements (input, textarea, contenteditable) -> text + * - Buttons and controls -> pointer + * - Images with onclick/links -> pointer + * 3. Check if the mouse is over a text node's bounding boxes to show text cursor only when + * actually over text characters. + */ + static std::string CURSOR_DETECTION_JS_SOURCE() { + return R"JS( +(function() { + if (window._flutterCursorDetectorInstalled) return; + window._flutterCursorDetectorInstalled = true; + + var lastCursor = ''; + + // Helper to check if element is editable + function isEditable(el) { + if (!el) return false; + var tagName = el.tagName ? el.tagName.toLowerCase() : ''; + + // Input fields (except hidden, button types, etc.) + if (tagName === 'input') { + var type = (el.type || 'text').toLowerCase(); + // These input types should show text cursor + var textTypes = ['text', 'password', 'email', 'number', 'search', + 'tel', 'url', 'date', 'datetime-local', 'month', + 'week', 'time']; + return textTypes.indexOf(type) !== -1; + } + + // Textarea + if (tagName === 'textarea') return true; + + // Contenteditable + if (el.isContentEditable) return true; + + return false; + } + + // Helper to check if element is a clickable control + function isClickable(el) { + if (!el) return false; + var tagName = el.tagName ? el.tagName.toLowerCase() : ''; + + // Links with href + if (tagName === 'a' && el.hasAttribute('href')) return true; + + // Buttons + if (tagName === 'button') return true; + + // Input buttons/submits + if (tagName === 'input') { + var type = (el.type || 'text').toLowerCase(); + if (['button', 'submit', 'reset', 'image', 'file', 'checkbox', 'radio'].indexOf(type) !== -1) { + return true; + } + } + + // Select dropdowns + if (tagName === 'select') return true; + + // Labels (clickable for form controls) + if (tagName === 'label') return true; + + // Summary in details element + if (tagName === 'summary') return true; + + // Elements with onclick handler or role="button" + if (el.onclick || el.getAttribute('role') === 'button' || + el.getAttribute('role') === 'link' || + el.getAttribute('role') === 'menuitem' || + el.getAttribute('role') === 'tab') { + return true; + } + + // Check for click event listeners via data attribute (common pattern) + if (el.dataset && (el.dataset.click || el.dataset.action)) return true; + + return false; + } + + // Determine effective cursor for "auto" mode + function getEffectiveCursor(el, computedCursor, mouseX, mouseY) { + // If cursor is explicitly set (not auto/default), use it + if (computedCursor && computedCursor !== 'auto' && computedCursor !== 'default') { + return computedCursor; + } + + if (!el) return 'default'; + + // Check element and ancestors for context + var current = el; + var maxDepth = 10; // Prevent infinite loops + var depth = 0; + + while (current && current !== document.body && depth < maxDepth) { + // Check if this element provides cursor context + if (isEditable(current)) { + return 'text'; + } + + if (isClickable(current)) { + return 'pointer'; + } + + current = current.parentElement; + depth++; + } + + // Use precise hit-testing to check if mouse is actually over text characters + // This avoids showing text cursor in padding/margin areas + if (isOverActualText(mouseX, mouseY)) { + // Double-check we're not in a clickable context + if (!isClickable(el) && !isClickableAncestor(el)) { + return 'text'; + } + } + + return 'default'; + } + + // Check if any ancestor is clickable (to avoid text cursor on buttons with text) + function isClickableAncestor(el) { + var current = el.parentElement; + var maxDepth = 5; + var depth = 0; + + while (current && current !== document.body && depth < maxDepth) { + if (isClickable(current)) return true; + current = current.parentElement; + depth++; + } + return false; + } + + // Check if mouse coordinates are actually over text characters + // https://stackoverflow.com/questions/10389459/is-there-a-way-to-detect-if-im-hovering-over-text + function isOverActualText(x, y) { + const element = document.elementFromPoint(x, y); + if (element == null) return false; + const nodes = element.childNodes; + for (let i = 0, node; (node = nodes[i++]); ) { + if (node.nodeType === 3) { + const range = document.createRange(); + range.selectNode(node); + const rects = range.getClientRects(); + for (let j = 0, rect; (rect = rects[j++]); ) { + if ( + x > rect.left && + x < rect.right && + y > rect.top && + y < rect.bottom + ) { + if (node.nodeType === Node.TEXT_NODE) return true; + } + } + } + } + return false; + } + + // Report cursor change to native + function reportCursor(cursor) { + if (cursor !== lastCursor) { + lastCursor = cursor; + try { + if (window.)JS" + + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + R"JS( && window.)JS" + + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + R"JS(.callHandler) { + window.)JS" + + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + R"JS(.callHandler('_cursorChanged', cursor); + } + } catch(_) {} + } + } + + // Handle mouse movement + function handleMouseMove(e) { + var el = document.elementFromPoint(e.clientX, e.clientY); + if (!el) { + reportCursor('default'); + return; + } + + // Get computed cursor style + var computedCursor = ''; + try { + computedCursor = window.getComputedStyle(el).cursor; + } catch(_) {} + + // Determine effective cursor (pass mouse coordinates for precise text detection) + var effectiveCursor = getEffectiveCursor(el, computedCursor, e.clientX, e.clientY); + reportCursor(effectiveCursor); + } + + // Use both mousemove and mouseover for comprehensive coverage + document.addEventListener('mousemove', handleMouseMove, { passive: true }); + + // Also handle mouseout to reset cursor when leaving elements + document.addEventListener('mouseout', function(e) { + if (e.relatedTarget === null) { + // Mouse left the document + reportCursor('default'); + } + }, { passive: true }); + +})(); +)JS"; + } + + /** + * Creates a PluginScript for cursor detection. + * + * This plugin runs in the main frame only to avoid performance overhead + * from multiple iframe instances. + */ + static std::unique_ptr CURSOR_DETECTION_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly = true) { + return std::make_unique( + CURSOR_DETECTION_JS_PLUGIN_SCRIPT_GROUP_NAME, + CURSOR_DETECTION_JS_SOURCE(), + UserScriptInjectionTime::atDocumentEnd, // Inject after DOM is ready + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CURSOR_DETECTION_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/date_input_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/date_input_js.h new file mode 100644 index 00000000..b0bbb1c4 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/date_input_js.h @@ -0,0 +1,286 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_DATE_INPUT_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_DATE_INPUT_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for intercepting date/time input elements. + * + * WPE WebKit doesn't have built-in date picker support, + * so we intercept clicks on date-related input elements and handle them + * natively via the JavaScript bridge. + * + * Supported input types: + * - date: Full date (YYYY-MM-DD) + * - datetime-local: Date and time (YYYY-MM-DDTHH:MM) + * - time: Time only (HH:MM) + * - month: Year and month (YYYY-MM) + * - week: Year and week (YYYY-Www) + */ +class DateInputJS { + public: + inline static const std::string DATE_INPUT_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_DATE_INPUT_JS_PLUGIN_SCRIPT"; + + /** + * JavaScript source code for date input interception. + * + * This script: + * 1. Injects CSS to ensure the calendar picker icon is visible and styled + * 2. Only intercepts clicks on the calendar icon (not the text field) + * 3. Allows users to type directly in the input field + * 4. Passes screen coordinates for proper popup positioning + */ + static std::string DATE_INPUT_JS_SOURCE() { + return R"JS( +(function() { + // Avoid re-registering + if (window._flutterInAppWebViewDateInputInit) return; + window._flutterInAppWebViewDateInputInit = true; + + // Supported date input types + var DATE_INPUT_TYPES = ['date', 'datetime-local', 'time', 'month', 'week']; + + // Icon mapping for different input types + var ICON_MAP = { + 'date': '📅', + 'datetime-local': '📅', + 'month': '📅', + 'week': '📅', + 'time': '🕐' + }; + + // Inject CSS to style the date/time inputs with a visible picker icon + // WebKit uses ::-webkit-calendar-picker-indicator for the icon + var style = document.createElement('style'); + style.textContent = [ + // Ensure the picker indicator is visible and clickable + 'input[type="date"]::-webkit-calendar-picker-indicator,', + 'input[type="datetime-local"]::-webkit-calendar-picker-indicator,', + 'input[type="time"]::-webkit-calendar-picker-indicator,', + 'input[type="month"]::-webkit-calendar-picker-indicator,', + 'input[type="week"]::-webkit-calendar-picker-indicator {', + ' cursor: pointer;', + ' opacity: 1;', + ' display: block;', + ' width: 20px;', + ' height: 20px;', + ' background: transparent;', + ' position: relative;', + '}', + // Style for our custom icon overlay + '._flutter_date_picker_icon {', + ' position: absolute;', + ' right: 4px;', + ' top: 50%;', + ' transform: translateY(-50%);', + ' cursor: pointer;', + ' font-size: 16px;', + ' user-select: none;', + ' pointer-events: auto;', + ' z-index: 1;', + ' padding: 2px 4px;', + ' opacity: 0.7;', + ' transition: opacity 0.15s;', + '}', + '._flutter_date_picker_icon:hover {', + ' opacity: 1;', + '}', + '._flutter_date_input_wrapper {', + ' position: relative;', + ' display: inline-block;', + '}', + '._flutter_date_input_wrapped {', + ' padding-right: 28px !important;', + '}' + ].join('\n'); + (document.head || document.documentElement).appendChild(style); + + // Track wrapped inputs to avoid double-wrapping + var wrappedInputs = new WeakSet(); + + // Wrap a date input with our custom icon + function wrapDateInput(input) { + if (wrappedInputs.has(input)) return; + if (input.disabled || input.readOnly) return; + + var inputType = input.attributes.type ? input.attributes.type.value : input.type; + if (DATE_INPUT_TYPES.indexOf(inputType) === -1) return; + + wrappedInputs.add(input); + + // Create wrapper if input is not already wrapped + var parent = input.parentNode; + if (!parent || parent.classList.contains('_flutter_date_input_wrapper')) return; + + // Create wrapper + var wrapper = document.createElement('span'); + wrapper.className = '_flutter_date_input_wrapper'; + wrapper.style.display = getComputedStyle(input).display === 'block' ? 'block' : 'inline-block'; + + // Insert wrapper and move input inside + parent.insertBefore(wrapper, input); + wrapper.appendChild(input); + + // Add padding to input for icon space + input.classList.add('_flutter_date_input_wrapped'); + + // Create icon + var icon = document.createElement('span'); + icon.className = '_flutter_date_picker_icon'; + icon.textContent = ICON_MAP[inputType] || '📅'; + icon.setAttribute('role', 'button'); + icon.setAttribute('aria-label', 'Open ' + inputType + ' picker'); + wrapper.appendChild(icon); + + // Handle icon click + icon.addEventListener('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + openDatePicker(input, event); + }, true); + + // Prevent default on the webkit picker indicator clicks + input.addEventListener('click', function(event) { + var rect = input.getBoundingClientRect(); + var clickX = event.clientX; + // If clicking in the rightmost 30px (icon area), open our picker + if (clickX > rect.right - 30) { + event.preventDefault(); + event.stopPropagation(); + openDatePicker(input, event); + } + // Otherwise let the user type in the field + }, true); + } + + // Function to get element's absolute position (relative to viewport and page) + function getElementRect(element) { + var rect = element.getBoundingClientRect(); + return { + // Position relative to page (with scroll) + x: Math.round(rect.left + window.scrollX), + y: Math.round(rect.top + window.scrollY), + width: Math.round(rect.width), + height: Math.round(rect.height), + // Position relative to viewport (without scroll) - for dialog positioning + viewportX: Math.round(rect.left), + viewportY: Math.round(rect.bottom + 2), // Position just below the input + viewportBottom: Math.round(rect.bottom) + }; + } + + // Open date picker for an input + function openDatePicker(input, event) { + if (input.disabled || input.readOnly) return; + + var inputType = input.attributes.type ? input.attributes.type.value : input.type; + if (DATE_INPUT_TYPES.indexOf(inputType) === -1) return; + + // Get current value + var currentValue = input.value || ''; + + // Get min/max constraints + var minValue = input.min || ''; + var maxValue = input.max || ''; + + // Get step attribute (for time inputs) + var step = input.step || ''; + + // Get element rect with screen coordinates + var elemRect = getElementRect(input); + + // Use the unified callHandler API through the JavaScript bridge + // This uses the with_reply API which works correctly for iframes + try { + window.)JS" + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + R"JS(.callHandler('_onDateInputClicked', { + inputType: inputType, + currentValue: currentValue, + minValue: minValue, + maxValue: maxValue, + step: step, + elemRect: elemRect + }).then(function(result) { + // Check if the result is a valid value (non-null, non-undefined) + if (result != null && result !== undefined) { + input.value = result; + // Dispatch input and change events to notify the page + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + } + // If result is null/undefined, user cancelled - do nothing + }).catch(function(e) { + console.error('Date picker error:', e); + }); + } catch(e) { + console.error('Failed to open date picker:', e); + } + } + + // Process existing date inputs on page load + function processExistingInputs() { + DATE_INPUT_TYPES.forEach(function(type) { + document.querySelectorAll('input[type="' + type + '"]').forEach(wrapDateInput); + }); + } + + // Observe for new date inputs added dynamically + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName === 'INPUT') { + wrapDateInput(node); + } else if (node.querySelectorAll) { + DATE_INPUT_TYPES.forEach(function(type) { + node.querySelectorAll('input[type="' + type + '"]').forEach(wrapDateInput); + }); + } + } + }); + }); + }); + + // Start observing when DOM is ready + if (document.body) { + processExistingInputs(); + observer.observe(document.body, { childList: true, subtree: true }); + } else { + document.addEventListener('DOMContentLoaded', function() { + processExistingInputs(); + observer.observe(document.body, { childList: true, subtree: true }); + }); + } +})(); +)JS"; + } + + /** + * Creates a PluginScript for date input interception. + */ + static std::unique_ptr DATE_INPUT_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly) { + return std::make_unique( + DATE_INPUT_JS_PLUGIN_SCRIPT_GROUP_NAME, DATE_INPUT_JS_SOURCE(), + UserScriptInjectionTime::atDocumentEnd, // Inject after DOM is ready + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + false, // requiredInAllContentWorlds + std::vector{} // messageHandlerNames - uses existing bridge + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_DATE_INPUT_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_ajax_request_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_ajax_request_js.h new file mode 100644 index 00000000..4a917e9d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_ajax_request_js.h @@ -0,0 +1,503 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_AJAX_REQUEST_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_AJAX_REQUEST_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for intercepting XMLHttpRequest (AJAX) requests. + * + * This script wraps XMLHttpRequest prototype methods (open, send, setRequestHeader) + * to intercept requests before they're sent and handle response callbacks. + * + * Matches iOS implementation: InterceptAjaxRequestJS.swift + */ +class InterceptAjaxRequestJS { + public: + inline static const std::string INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"; + + /** + * Flag variable name used to enable/disable AJAX request interception at runtime. + */ + static std::string FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._useShouldInterceptAjaxRequest"; + } + + /** + * Flag variable for onAjaxReadyStateChange callback. + */ + static std::string FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._useOnAjaxReadyStateChange"; + } + + /** + * Flag variable for onAjaxProgress callback. + */ + static std::string FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._useOnAjaxProgress"; + } + + /** + * Flag variable for intercepting only async AJAX requests. + */ + static std::string FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._interceptOnlyAsyncAjaxRequests"; + } + + /** + * JavaScript utility variable name reference. + */ + static std::string JAVASCRIPT_UTIL_VAR_NAME() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "._util"; + } + + /** + * JavaScript source code for utility functions needed by AJAX interception. + * This must be injected before the AJAX interception script. + */ + static std::string JAVASCRIPT_UTIL_JS_SOURCE() { + const std::string utilVarName = JAVASCRIPT_UTIL_VAR_NAME(); + + return utilVarName + R"JS( = { + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') === 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.split('\r\n')[0].trim(); + var contentType = 'multipart/form-data; boundary=' + boundary.substring(2); + return contentType; + }, + convertBodyRequest: function(body) { + return new Promise(function(resolve, reject) { + if (body == null) { + resolve(null); + return; + } + if (body instanceof ArrayBuffer) { + resolve(Array.from(new Uint8Array(body))); + return; + } + if (ArrayBuffer.isView(body)) { + resolve(Array.from(new Uint8Array(body.buffer))); + return; + } + if (body instanceof Blob) { + var reader = new FileReader(); + reader.addEventListener('loadend', function() { + resolve(Array.from(new Uint8Array(reader.result))); + }); + reader.readAsArrayBuffer(body); + return; + } + if (body instanceof FormData) { + var entries = body.entries(); + var dataString = ''; + var boundary = '------WebKitFormBoundary' + Math.random().toString(36).substring(7); + var entry = entries.next(); + while (!entry.done) { + var name = entry.value[0]; + var value = entry.value[1]; + dataString += boundary + '\r\n'; + if (value instanceof File) { + dataString += 'Content-Disposition: form-data; name="' + name + '"; filename="' + value.name + '"\r\n'; + dataString += 'Content-Type: ' + value.type + '\r\n\r\n'; + dataString += value.data + '\r\n'; + } else { + dataString += 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n'; + dataString += value + '\r\n'; + } + entry = entries.next(); + } + if (dataString.length > 0) { + dataString += boundary + '--\r\n'; + } + resolve(dataString); + return; + } + resolve(body.toString()); + }); + }, + arrayBufferToString: function(arrayBuffer) { + var decoder = new TextDecoder('utf-8'); + return decoder.decode(new Uint8Array(arrayBuffer)); + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + if (headers instanceof Headers) { + headers.forEach(function(value, key) { + headersObj[key] = value; + }); + } + return headersObj; + }, + convertJsonToHeaders: function(headersObj) { + var headers = new Headers(); + for (var key in headersObj) { + headers.append(key, headersObj[key]); + } + return headers; + }, + convertCredentialsToJson: function(credentials) { + var credentialsObj = {}; + if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.protocol = credentials.protocol; + credentialsObj.provider = credentials.provider; + credentialsObj.iconURL = credentials.iconURL; + } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.password = credentials.password; + credentialsObj.iconURL = credentials.iconURL; + } else { + credentialsObj.type = 'default'; + credentialsObj.value = credentials; + } + return credentialsObj; + }, + convertJsonToCredential: function(credentialsJson) { + var credentials; + if (window.FederatedCredential != null && credentialsJson.type === 'federated') { + credentials = new FederatedCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + protocol: credentialsJson.protocol, + provider: credentialsJson.provider, + iconURL: credentialsJson.iconURL + }); + } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { + credentials = new PasswordCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + password: credentialsJson.password, + iconURL: credentialsJson.iconURL + }); + } else { + credentials = credentialsJson.value == null ? undefined : credentialsJson.value; + } + return credentials; + } +}; +)JS"; + } + + /** + * JavaScript source code for AJAX request interception. + * Wraps XMLHttpRequest prototype methods to intercept requests. + * + * Matches iOS InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE() + * + * @param initialUseOnAjaxReadyStateChange Initial value for onAjaxReadyStateChange flag. + * @param initialUseOnAjaxProgress Initial value for onAjaxProgress flag. + */ + static std::string INTERCEPT_AJAX_REQUEST_JS_SOURCE(bool initialUseOnAjaxReadyStateChange, + bool initialUseOnAjaxProgress) { + const std::string flagShouldIntercept = FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE(); + const std::string flagReadyStateChange = FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE(); + const std::string flagProgress = FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS(); + const std::string flagOnlyAsync = FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE(); + const std::string utilVarName = JAVASCRIPT_UTIL_VAR_NAME(); + const std::string bridgeName = JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME(); + + return JAVASCRIPT_UTIL_JS_SOURCE() + + flagShouldIntercept + " = true;\n" + + flagReadyStateChange + " = " + (initialUseOnAjaxReadyStateChange ? "true" : "false") + ";\n" + + flagProgress + " = " + (initialUseOnAjaxProgress ? "true" : "false") + ";\n" + + flagOnlyAsync + " = true;\n" + + R"JS( +(function(ajax) { + var send = ajax.prototype.send; + var open = ajax.prototype.open; + var setRequestHeader = ajax.prototype.setRequestHeader; + ajax.prototype._flutter_inappwebview_url = null; + ajax.prototype._flutter_inappwebview_method = null; + ajax.prototype._flutter_inappwebview_isAsync = null; + ajax.prototype._flutter_inappwebview_user = null; + ajax.prototype._flutter_inappwebview_password = null; + ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false; + ajax.prototype._flutter_inappwebview_request_headers = {}; + + function convertRequestResponse(request, callback) { + if (request.response != null && request.responseType != null) { + switch (request.responseType) { + case 'arraybuffer': + callback(Array.from(new Uint8Array(request.response))); + return; + case 'blob': + var reader = new FileReader(); + reader.addEventListener('loadend', function() { + callback(Array.from(new Uint8Array(reader.result))); + }); + reader.readAsArrayBuffer(request.response); + return; + case 'document': + callback(request.response.documentElement.outerHTML); + return; + case 'json': + callback(request.response); + return; + } + } + callback(null); + } + + ajax.prototype.open = function(method, url, isAsync, user, password) { + isAsync = (isAsync != null) ? isAsync : true; + this._flutter_inappwebview_url = url; + this._flutter_inappwebview_method = method; + this._flutter_inappwebview_isAsync = isAsync; + this._flutter_inappwebview_user = user; + this._flutter_inappwebview_password = password; + this._flutter_inappwebview_request_headers = {}; + open.call(this, method, url, isAsync, user, password); + }; + + ajax.prototype.setRequestHeader = function(header, value) { + this._flutter_inappwebview_request_headers[header] = value; + setRequestHeader.call(this, header, value); + }; + + function handleEvent(e) { + if ()JS" + flagShouldIntercept + R"JS( === false || )JS" + flagProgress + R"JS( == null || )JS" + flagProgress + R"JS( === false) { + return; + } + var self = this; + if ()JS" + flagShouldIntercept + R"JS( == null || )JS" + flagShouldIntercept + R"JS( == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\r\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); + } + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders: responseHeaders, + event: { + type: e.type, + loaded: e.loaded, + lengthComputable: e.lengthComputable, + total: e.total + } + }; + window.)JS" + bridgeName + R"JS(.callHandler('onAjaxProgress', ajaxRequest).then(function(result) { + if (result != null) { + try { + result = JSON.parse(result); + } catch(e) {} + if (result === 0) { + self.abort(); + return; + } + } + }); + }); + } + } + + ajax.prototype.send = function(data) { + var self = this; + var canBeIntercepted = self._flutter_inappwebview_isAsync || )JS" + flagOnlyAsync + R"JS( === false; + if (canBeIntercepted && ()JS" + flagShouldIntercept + R"JS( == null || )JS" + flagShouldIntercept + R"JS( == true)) { + if ()JS" + flagReadyStateChange + R"JS( === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) { + this._flutter_inappwebview_already_onreadystatechange_wrapped = true; + var realOnreadystatechange = this.onreadystatechange; + this.onreadystatechange = function() { + if ()JS" + flagShouldIntercept + R"JS( == null || )JS" + flagShouldIntercept + R"JS( == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\r\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); + } + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders: responseHeaders + }; + window.)JS" + bridgeName + R"JS(.callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { + if (result != null) { + try { + result = JSON.parse(result); + } catch(e) {} + if (result === 0) { + self.abort(); + return; + } + } + if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }); + }); + } else if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }; + } + this.addEventListener('loadstart', handleEvent); + this.addEventListener('load', handleEvent); + this.addEventListener('loadend', handleEvent); + this.addEventListener('progress', handleEvent); + this.addEventListener('error', handleEvent); + this.addEventListener('abort', handleEvent); + this.addEventListener('timeout', handleEvent); + + )JS" + utilVarName + R"JS(.convertBodyRequest(data).then(function(convertedData) { + var ajaxRequest = { + data: convertedData, + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + responseType: self.responseType + }; + window.)JS" + bridgeName + R"JS(.callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + try { + result = JSON.parse(result); + } catch(e) {} + if (result === 0) { + self.abort(); + return; + } + if (result.data != null && !)JS" + utilVarName + R"JS(.isString(result.data) && result.data.length > 0) { + var bodyString = )JS" + utilVarName + R"JS(.arrayBufferToString(result.data); + if ()JS" + utilVarName + R"JS(.isBodyFormData(bodyString)) { + var formDataContentType = )JS" + utilVarName + R"JS(.getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + if ()JS" + utilVarName + R"JS(.isString(result.data) || result.data == null) { + convertedData = result.data; + } else if (result.data.length > 0) { + convertedData = new Uint8Array(result.data); + } + self.withCredentials = result.withCredentials; + if (result.responseType != null && self._flutter_inappwebview_isAsync) { + self.responseType = result.responseType; + } + if (result.headers != null) { + for (var header in result.headers) { + var value = result.headers[header]; + var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; + if (flutter_inappwebview_value == null) { + self._flutter_inappwebview_request_headers[header] = value; + } else { + self._flutter_inappwebview_request_headers[header] += ', ' + value; + } + setRequestHeader.call(self, header, value); + } + } + if ((self._flutter_inappwebview_method != result.method && result.method != null) || + (self._flutter_inappwebview_url != result.url && result.url != null) || + (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) || + (self._flutter_inappwebview_user != result.user && result.user != null) || + (self._flutter_inappwebview_password != result.password && result.password != null)) { + self.abort(); + self.open(result.method, result.url, result.isAsync, result.user, result.password); + } + } + send.call(self, convertedData); + }); + }); + } else { + send.call(this, data); + } + }; +})(window.XMLHttpRequest); +)JS"; + } + + /** + * Creates a PluginScript for AJAX request interception. + * + * @param allowedOriginRules Optional list of origin rules to restrict script injection. + * @param forMainFrameOnly Whether to inject only in main frame. + * @param initialUseOnAjaxReadyStateChange Initial value for onAjaxReadyStateChange flag. + * @param initialUseOnAjaxProgress Initial value for onAjaxProgress flag. + */ + static std::unique_ptr INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly, + bool initialUseOnAjaxReadyStateChange = false, + bool initialUseOnAjaxProgress = false) { + return std::make_unique( + INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress), + UserScriptInjectionTime::atDocumentStart, + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_AJAX_REQUEST_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_fetch_request_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_fetch_request_js.h new file mode 100644 index 00000000..e4b53e90 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/intercept_fetch_request_js.h @@ -0,0 +1,216 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_FETCH_REQUEST_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_FETCH_REQUEST_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" +#include "intercept_ajax_request_js.h" // For JAVASCRIPT_UTIL_VAR_NAME + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for intercepting fetch() requests. + * + * This script wraps the global fetch() function to intercept requests + * before they're sent and allow modification of the request/response. + * + * Matches iOS implementation: InterceptFetchRequestJS.swift + */ +class InterceptFetchRequestJS { + public: + inline static const std::string INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"; + + /** + * Flag variable name used to enable/disable fetch request interception at runtime. + */ + static std::string FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._useShouldInterceptFetchRequest"; + } + + /** + * JavaScript source code for fetch request interception. + * Wraps the global fetch() function to intercept requests. + * + * Matches iOS InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE() + */ + static std::string INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + const std::string flagIntercept = FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE(); + const std::string utilVarName = InterceptAjaxRequestJS::JAVASCRIPT_UTIL_VAR_NAME(); + const std::string bridgeName = JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME(); + + // Ensure utility functions exist (may already be defined by AJAX script) + std::string utilitySetup = R"JS( +if (typeof )JS" + utilVarName + R"JS( === 'undefined') { + )JS" + InterceptAjaxRequestJS::JAVASCRIPT_UTIL_JS_SOURCE() + R"JS( +} +)JS"; + + return utilitySetup + flagIntercept + R"JS( = true; +(function(fetch) { + if (fetch == null) { + return; + } + window.fetch = async function(resource, init) { + if ()JS" + flagIntercept + R"JS( == null || )JS" + flagIntercept + R"JS( == true) { + var fetchRequest = { + url: null, + method: null, + headers: null, + body: null, + mode: null, + credentials: null, + cache: null, + redirect: null, + referrer: null, + referrerPolicy: null, + integrity: null, + keepalive: null + }; + if (resource instanceof Request) { + fetchRequest.url = resource.url; + fetchRequest.method = resource.method; + fetchRequest.headers = resource.headers; + fetchRequest.body = resource.body; + fetchRequest.mode = resource.mode; + fetchRequest.credentials = resource.credentials; + fetchRequest.cache = resource.cache; + fetchRequest.redirect = resource.redirect; + fetchRequest.referrer = resource.referrer; + fetchRequest.referrerPolicy = resource.referrerPolicy; + fetchRequest.integrity = resource.integrity; + fetchRequest.keepalive = resource.keepalive; + } else { + fetchRequest.url = resource != null ? resource.toString() : null; + if (init != null) { + fetchRequest.method = init.method; + fetchRequest.headers = init.headers; + fetchRequest.body = init.body; + fetchRequest.mode = init.mode; + fetchRequest.credentials = init.credentials; + fetchRequest.cache = init.cache; + fetchRequest.redirect = init.redirect; + fetchRequest.referrer = init.referrer; + fetchRequest.referrerPolicy = init.referrerPolicy; + fetchRequest.integrity = init.integrity; + fetchRequest.keepalive = init.keepalive; + } + } + if (fetchRequest.headers instanceof Headers) { + fetchRequest.headers = )JS" + utilVarName + R"JS(.convertHeadersToJson(fetchRequest.headers); + } + fetchRequest.credentials = )JS" + utilVarName + R"JS(.convertCredentialsToJson(fetchRequest.credentials); + return )JS" + utilVarName + R"JS(.convertBodyRequest(fetchRequest.body).then(function(body) { + fetchRequest.body = body; + return window.)JS" + bridgeName + R"JS(.callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { + if (result != null) { + try { + result = JSON.parse(result); + } catch(e) {} + if (result != null && result.action === 0) { + var controller = new AbortController(); + if (init != null) { + init.signal = controller.signal; + } else { + init = { + signal: controller.signal + }; + } + controller.abort(); + return fetch(resource, init); + } + if (result != null) { + if (result.body != null && !)JS" + utilVarName + R"JS(.isString(result.body) && result.body.length > 0) { + var bodyString = )JS" + utilVarName + R"JS(.arrayBufferToString(result.body); + if ()JS" + utilVarName + R"JS(.isBodyFormData(bodyString)) { + var formDataContentType = )JS" + utilVarName + R"JS(.getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + resource = result.url; + if (init == null) { + init = {}; + } + if (result.method != null && result.method.length > 0) { + init.method = result.method; + } + if (result.headers != null && Object.keys(result.headers).length > 0) { + init.headers = )JS" + utilVarName + R"JS(.convertJsonToHeaders(result.headers); + } + if ()JS" + utilVarName + R"JS(.isString(result.body) || result.body == null) { + init.body = result.body; + } else if (result.body.length > 0) { + init.body = new Uint8Array(result.body); + } + if (result.mode != null && result.mode.length > 0) { + init.mode = result.mode; + } + if (result.credentials != null) { + init.credentials = )JS" + utilVarName + R"JS(.convertJsonToCredential(result.credentials); + } + if (result.cache != null && result.cache.length > 0) { + init.cache = result.cache; + } + if (result.redirect != null && result.redirect.length > 0) { + init.redirect = result.redirect; + } + if (result.referrer != null && result.referrer.length > 0) { + init.referrer = result.referrer; + } + if (result.referrerPolicy != null && result.referrerPolicy.length > 0) { + init.referrerPolicy = result.referrerPolicy; + } + if (result.integrity != null && result.integrity.length > 0) { + init.integrity = result.integrity; + } + if (result.keepalive != null) { + init.keepalive = result.keepalive; + } + return fetch(resource, init); + } + } + return fetch(resource, init); + }); + }); + } else { + return fetch(resource, init); + } + }; +})(window.fetch); +)JS"; + } + + /** + * Creates a PluginScript for fetch request interception. + * + * @param allowedOriginRules Optional list of origin rules to restrict script injection. + * @param forMainFrameOnly Whether to inject only in main frame. + */ + static std::unique_ptr INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly) { + return std::make_unique( + INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + UserScriptInjectionTime::atDocumentStart, + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_INTERCEPT_FETCH_REQUEST_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/javascript_bridge_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/javascript_bridge_js.h new file mode 100644 index 00000000..37f752cf --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/javascript_bridge_js.h @@ -0,0 +1,140 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "../utils/string.h" + +namespace flutter_inappwebview_plugin { + +// Forward declaration to avoid circular dependency +class WindowIdJS; + +/** + * JavaScript bridge for communication between web content and native code. + * + * This implementation matches iOS JavaScriptBridgeJS.swift for consistency. + * It uses webkit.messageHandlers to communicate with native code and includes + * support for multi-window scenarios via _windowId. + */ +class JavaScriptBridgeJS { + public: + static void set_JAVASCRIPT_BRIDGE_NAME(const std::string& bridgeName) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName; + } + + static std::string get_JAVASCRIPT_BRIDGE_NAME() { return _JAVASCRIPT_BRIDGE_NAME; } + + inline static const std::string JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; + + inline static const std::string VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = + "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET"; + + /** + * Returns the JavaScript variable name for the window ID. + * Match iOS WindowIdJS: "window._flutter_inappwebview_windowId" + */ + static std::string WINDOW_ID_VARIABLE_JS_SOURCE() { + return "window._" + get_JAVASCRIPT_BRIDGE_NAME() + "_windowId"; + } + + /** + * JavaScript source code for the bridge. + * This code sets up window.flutter_inappwebview.callHandler() function + * which communicates with native code via webkit.messageHandlers. + * + * Matches iOS JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE() + */ + static std::string JAVASCRIPT_BRIDGE_JS_SOURCE() { + return R"JS( +window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS( = {}; +window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS(._webMessageChannels = {}; +(function(window) { + var bridgeSecret = ')JS" + + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + R"JS('; + var _JSON_stringify; + var _Array_slice; + var _UserMessageHandler; + var _postMessage; + + try { + _JSON_stringify = window.JSON.stringify; + _Array_slice = window.Array.prototype.slice; + _Array_slice.call = window.Function.prototype.call; + _UserMessageHandler = window.webkit.messageHandlers['callHandler']; + _postMessage = _UserMessageHandler.postMessage; + _postMessage.call = window.Function.prototype.call; + } catch (_) { return; } + + window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS(.callHandler = function() { + var _windowId = )JS" + + WINDOW_ID_VARIABLE_JS_SOURCE() + R"JS(; + // Use with_reply API - postMessage returns a Promise directly + return _postMessage.call(_UserMessageHandler, { + 'handlerName': arguments[0], + '_bridgeSecret': bridgeSecret, + 'args': _JSON_stringify(_Array_slice.call(arguments, 1)), + '_windowId': _windowId, + '_isMainFrame': (window.top === window) + }); + }; +})(window); +)JS"; + } + + /** + * JavaScript to dispatch the platform ready event. + */ + static std::string PLATFORM_READY_JS_SOURCE() { + return R"JS( +(function() { + if ((window.top == null || window.top === window) && + window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS( != null && + window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS(._platformReady == null) { + window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady')); + window.)JS" + + get_JAVASCRIPT_BRIDGE_NAME() + R"JS(._platformReady = true; + } +})(); +)JS"; + } + + /** + * Creates a PluginScript for the JavaScript bridge. + */ + static std::unique_ptr JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT( + const std::string& expectedBridgeSecret, + const std::optional>& allowedOriginRules, bool forMainFrameOnly) { + std::string source = JAVASCRIPT_BRIDGE_JS_SOURCE(); + // Replace the placeholder with the actual secret + size_t pos = source.find(VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET); + if (pos != std::string::npos) { + source.replace(pos, VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET.length(), expectedBridgeSecret); + } + + return std::make_unique( + JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, source, + UserScriptInjectionTime::atDocumentStart, forMainFrameOnly, allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{"callHandler"} // messageHandlerNames + ); + } + + private: + inline static std::string _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/on_load_resource_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/on_load_resource_js.h new file mode 100644 index 00000000..489f6c00 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/on_load_resource_js.h @@ -0,0 +1,89 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_ON_LOAD_RESOURCE_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_ON_LOAD_RESOURCE_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for capturing resource load events. + * + * This script uses the PerformanceObserver API to monitor resource loading + * and sends the data to the native side via the JavaScript bridge. + * + * Matches iOS implementation: OnLoadResourceJS.swift + */ +class OnLoadResourceJS { + public: + inline static const std::string ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"; + + /** + * Flag variable name used to enable/disable the observer at runtime. + */ + static std::string FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._useOnLoadResource"; + } + + /** + * JavaScript source code for resource loading observation. + * Uses PerformanceObserver API to track all resource loads. + * + * Matches iOS OnLoadResourceJS.swift implementation. + */ + static std::string ON_LOAD_RESOURCE_JS_SOURCE() { + const std::string flagVariable = FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE(); + const std::string bridgeName = JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME(); + + return flagVariable + R"JS( = true; +(function() { + var observer = new PerformanceObserver(function(list) { + list.getEntries().forEach(function(entry) { + if ()JS" + flagVariable + R"JS( == null || )JS" + flagVariable + R"JS( == true) { + var resource = { + "url": entry.name, + "initiatorType": entry.initiatorType, + "startTime": entry.startTime, + "duration": entry.duration + }; + window.)JS" + bridgeName + R"JS(.callHandler("onLoadResource", resource); + } + }); + }); + observer.observe({entryTypes: ['resource']}); +})(); +)JS"; + } + + /** + * Creates a PluginScript for resource load observation. + * + * @param allowedOriginRules Optional list of origin rules to restrict script injection. + * @param forMainFrameOnly Whether to inject only in main frame. + */ + static std::unique_ptr ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly) { + return std::make_unique( + ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + ON_LOAD_RESOURCE_JS_SOURCE(), + UserScriptInjectionTime::atDocumentStart, + forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + false, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_ON_LOAD_RESOURCE_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/print_interception_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/print_interception_js.h new file mode 100644 index 00000000..e5fba6b9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/print_interception_js.h @@ -0,0 +1,93 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PRINT_INTERCEPTION_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PRINT_INTERCEPTION_JS_H_ + +#include +#include +#include +#include + +#include "../types/plugin_script.h" +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for intercepting window.print() calls. + * + * WPE WebKit doesn't have a native print signal like WebKitGTK, + * so we intercept window.print() calls via JavaScript and notify + * the Dart side via the JavaScript bridge. + * + * This allows the app to: + * - Know when a page tries to print + * - Optionally implement print-to-PDF functionality + * - Handle print requests in a platform-appropriate way + */ +class PrintInterceptionJS { + public: + inline static const std::string PRINT_INTERCEPTION_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_PRINT_INTERCEPTION_JS_PLUGIN_SCRIPT"; + + /** + * JavaScript source code for print interception. + * This code intercepts window.print() calls and notifies the native side. + */ + static std::string PRINT_INTERCEPTION_JS_SOURCE() { + return R"JS( +(function() { + // Avoid re-registering + if (window._flutterInAppWebViewPrintInterceptionInit) return; + window._flutterInAppWebViewPrintInterceptionInit = true; + + // Store the original window.print function + var originalPrint = window.print; + + // Override window.print + window.print = function() { + // Check if the JavaScript bridge is available + var bridge = window.)JS" + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + R"JS(; + if (bridge && typeof bridge.callHandler === 'function') { + // Notify the native side about the print request + bridge.callHandler('_onPrintRequest', { + url: window.location.href, + title: document.title + }).then(function(result) { + // If the native side returns true, proceed with original print + // Otherwise, the native side handled the print request + if (result === true) { + originalPrint.call(window); + } + }).catch(function(error) { + // On error, fall back to original print behavior + console.warn('Print interception error:', error); + originalPrint.call(window); + }); + } else { + // No bridge available, use original print + originalPrint.call(window); + } + }; +})(); +)JS"; + } + + /** + * Creates a PluginScript for print interception. + */ + static std::unique_ptr PRINT_INTERCEPTION_JS_PLUGIN_SCRIPT( + const std::optional>& allowedOriginRules, + bool forMainFrameOnly) { + return std::make_unique( + PRINT_INTERCEPTION_JS_PLUGIN_SCRIPT_GROUP_NAME, PRINT_INTERCEPTION_JS_SOURCE(), + UserScriptInjectionTime::atDocumentStart, forMainFrameOnly, + allowedOriginRules, + nullptr, // contentWorld + true, // requiredInAllContentWorlds + std::vector{} // no additional message handlers needed + ); + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_PRINT_INTERCEPTION_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_channel_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_channel_js.h new file mode 100644 index 00000000..bb316a19 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_channel_js.h @@ -0,0 +1,150 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_JS_H_ + +#include + +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript support for WebMessageChannel. + * + * Provides the variable name for storing MessageChannels in JavaScript + * and JavaScript snippets for creating and managing WebMessageChannels. + * Matches iOS WebMessageChannelJS.swift for consistency. + */ +class WebMessageChannelJS { + public: + /** + * Returns the JavaScript variable name for storing WebMessageChannels. + * Example: "window.flutter_inappwebview._webMessageChannels" + */ + static std::string WEB_MESSAGE_CHANNELS_VARIABLE_NAME() { + return "window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + + "._webMessageChannels"; + } + + /** + * JavaScript to create a new MessageChannel and store it. + * + * @param channelId The unique identifier for the channel + * @return JavaScript code that creates the channel and returns its info + */ + static std::string createWebMessageChannelJs(const std::string& channelId) { + return + "(function() {\n" + " var channel = new MessageChannel();\n" + " " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'] = channel;\n" + " return {'id': '" + channelId + "'};\n" + "})();"; + } + + /** + * JavaScript to set the onmessage callback for a port. + * + * @param channelId The channel identifier + * @param portIndex The port index (0 or 1) + * @return JavaScript code that sets up the callback + */ + static std::string setWebMessageCallbackJs(const std::string& channelId, int portIndex) { + std::string portName = portIndex == 0 ? "port1" : "port2"; + return + "(function() {\n" + " var webMessageChannel = " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'];\n" + " if (webMessageChannel != null) {\n" + " webMessageChannel." + portName + ".onmessage = function(event) {\n" + " " + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWebMessagePortMessageReceived', {\n" + " 'webMessageChannelId': '" + channelId + "',\n" + " 'index': " + std::to_string(portIndex) + ",\n" + " 'message': {\n" + " 'data': window.ArrayBuffer != null && event.data instanceof ArrayBuffer\n" + " ? Array.from(new Uint8Array(event.data))\n" + " : (event.data != null ? event.data.toString() : null),\n" + " 'type': window.ArrayBuffer != null && event.data instanceof ArrayBuffer ? 1 : 0\n" + " }\n" + " });\n" + " };\n" + " webMessageChannel." + portName + ".start();\n" + " }\n" + "})();"; + } + + /** + * JavaScript to post a message on a port. + * + * @param channelId The channel identifier + * @param portIndex The port index (0 or 1) + * @param messageDataJs The message data as JavaScript expression + * @return JavaScript code that posts the message + */ + static std::string postMessageJs(const std::string& channelId, int portIndex, + const std::string& messageDataJs) { + std::string portName = portIndex == 0 ? "port1" : "port2"; + return + "(function() {\n" + " var webMessageChannel = " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'];\n" + " if (webMessageChannel != null) {\n" + " webMessageChannel." + portName + ".postMessage(" + messageDataJs + ");\n" + " }\n" + "})();"; + } + + /** + * JavaScript to close a port. + * + * @param channelId The channel identifier + * @param portIndex The port index (0 or 1) + * @return JavaScript code that closes the port + */ + static std::string closePortJs(const std::string& channelId, int portIndex) { + std::string portName = portIndex == 0 ? "port1" : "port2"; + return + "(function() {\n" + " var webMessageChannel = " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'];\n" + " if (webMessageChannel != null) {\n" + " webMessageChannel." + portName + ".close();\n" + " }\n" + "})();"; + } + + /** + * JavaScript to dispose of a channel (clean up both ports and remove from storage). + * + * @param channelId The channel identifier + * @return JavaScript code that disposes the channel + */ + static std::string disposeChannelJs(const std::string& channelId) { + return + "(function() {\n" + " var webMessageChannel = " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'];\n" + " if (webMessageChannel != null) {\n" + " try { webMessageChannel.port1.close(); } catch(e) {}\n" + " try { webMessageChannel.port2.close(); } catch(e) {}\n" + " delete " + WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + channelId + "'];\n" + " }\n" + "})();"; + } + + /** + * JavaScript to post a WebMessage to the window, optionally with ports. + * + * @param messageDataJs The message data as JavaScript expression + * @param targetOrigin The target origin string + * @param portsJs Optional JavaScript expression for ports array (empty string if no ports) + * @return JavaScript code that posts the message + */ + static std::string postWebMessageJs(const std::string& messageDataJs, + const std::string& targetOrigin, + const std::string& portsJs) { + std::string portsArg = portsJs.empty() ? "undefined" : portsJs; + return + "(function() {\n" + " window.postMessage(" + messageDataJs + ", '" + targetOrigin + "', " + portsArg + ");\n" + "})();"; + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_listener_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_listener_js.h new file mode 100644 index 00000000..2f54fc49 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/web_message_listener_js.h @@ -0,0 +1,186 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_JS_H_ + +#include + +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript source for WebMessageListener support. + * + * This creates the FlutterInAppWebViewWebMessageListener class that web pages + * can use to communicate with the native side via postMessage. + * + * Usage in JavaScript: + * myListener.postMessage("Hello from web!"); + * myListener.addEventListener("message", (event) => { ... }); + * + * Matches iOS WebMessageListenerJS.swift for consistency. + */ +class WebMessageListenerJS { + public: + /** + * JavaScript source code for the FlutterInAppWebViewWebMessageListener class. + */ + static std::string WEB_MESSAGE_LISTENER_JS_SOURCE() { + return R"JS( +function FlutterInAppWebViewWebMessageListener(jsObjectName) { + this.jsObjectName = jsObjectName; + this.listeners = []; + this.onmessage = null; +} +FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) { + var message = { + "data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null), + "type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0 + }; + window.)JS" + + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + R"JS(.callHandler('onWebMessageListenerPostMessageReceived', { + jsObjectName: this.jsObjectName, + message: message, + sourceOrigin: window.location.origin, + isMainFrame: window.top === window + }); +}; +FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { + if (listener == null) { + return; + } + this.listeners.push(listener); +}; +FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) { + if (listener == null) { + return; + } + var index = this.listeners.indexOf(listener); + if (index >= 0) { + this.listeners.splice(index, 1); + } +}; +)JS"; + } + + /** + * JavaScript to check if origin is allowed based on origin rules. + * + * Origin rules format: + * - "*" matches all origins + * - "https://example.com" matches exact origin + * - "https: // *.example.com" matches subdomains (note: no space in actual use) + */ + static std::string IS_ORIGIN_ALLOWED_JS_SOURCE() { + // Note: Using string concatenation to avoid compiler warning about + // regex patterns like /[0-9]+/g being interpreted as C comments. + return + "var _normalizeIPv6 = function(ip_string) {\n" + " var ipv4 = ip_string.match(/(.*)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);\n" + " if (ipv4) {\n" + " ip_string = ipv4[1];\n" + " ipv4 = ipv4[2].match(/[0-9]+/" "g);\n" // Split to avoid /* comment warning + " for (var i = 0;i < 4;i ++) {\n" + " var byte = parseInt(ipv4[i],10);\n" + " ipv4[i] = ('0' + byte.toString(16)).substr(-2);\n" + " }\n" + " ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];\n" + " }\n" + " ip_string = ip_string.replace(/^:|:$/" "g, '');\n" // Split to avoid /* comment warning + " var ipv6 = ip_string.split(':');\n" + " for (var i = 0; i < ipv6.length; i ++) {\n" + " var hex = ipv6[i];\n" + " if (hex != '') {\n" + " ipv6[i] = ('0000' + hex).substr(-4);\n" + " }\n" + " else {\n" + " hex = [];\n" + " for (var j = ipv6.length; j <= 8; j ++) {\n" + " hex.push('0000');\n" + " }\n" + " ipv6[i] = hex.join(':');\n" + " }\n" + " }\n" + " return ipv6.join(':');\n" + "};\n" + "\n" + "var _isOriginAllowed = function(allowedOriginRules, scheme, host, port) {\n" + " for (var rule of allowedOriginRules) {\n" + " if (rule === '*') {\n" + " return true;\n" + " }\n" + " if (scheme == null || scheme === '') {\n" + " continue;\n" + " }\n" + " if ((scheme == null || scheme === '') && (host == null || host === '') && (port === 0 || port === '' || port == null)) {\n" + " continue;\n" + " }\n" + " var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == 'https' ? 443 : 80) : rule.port;\n" + " var currentPort = port === 0 || port === '' || port == null ? (scheme == 'https' ? 443 : 80) : port;\n" + " var IPv6 = null;\n" + " if (rule.host != null && rule.host[0] === '[') {\n" + " try {\n" + " IPv6 = _normalizeIPv6(rule.host.substring(1, rule.host.length - 1));\n" + " } catch(e) {}\n" + " }\n" + " var hostIPv6 = null;\n" + " try {\n" + " hostIPv6 = _normalizeIPv6(host);\n" + " } catch(e) {}\n" + "\n" + " var schemeAllowed = scheme == rule.scheme;\n" + " \n" + " var hostAllowed = rule.host == null ||\n" + " rule.host === '' ||\n" + " host === rule.host ||\n" + " (rule.host[0] === '*' && host != null && host.indexOf(rule.host.split('*')[1]) >= 0) ||\n" + " (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);\n" + "\n" + " var portAllowed = rulePort === currentPort;\n" + "\n" + " if (schemeAllowed && hostAllowed && portAllowed) {\n" + " return true;\n" + " }\n" + " }\n" + " return false;\n" + "};\n"; + } + + /** + * Creates the JavaScript to inject a web message listener with the given name and origin rules. + * + * @param jsObjectName The name of the JavaScript object to inject (e.g., "myListener") + * @param allowedOriginRulesJs JSON array string of allowed origin rules + */ + static std::string createWebMessageListenerInjectionJs( + const std::string& jsObjectName, + const std::string& allowedOriginRulesJs) { + std::string jsObjectNameEscaped = jsObjectName; + // Escape single quotes in jsObjectName + size_t pos = 0; + while ((pos = jsObjectNameEscaped.find("'", pos)) != std::string::npos) { + jsObjectNameEscaped.replace(pos, 1, "\\'"); + pos += 2; + } + + return "(function() {\n" + IS_ORIGIN_ALLOWED_JS_SOURCE() + "\n" + + WEB_MESSAGE_LISTENER_JS_SOURCE() + + "\nvar allowedOriginRules = " + allowedOriginRulesJs + + ";\n" + "var isPageBlank = window.location.href === 'about:blank';\n" + "var scheme = !isPageBlank ? window.location.protocol.replace(':', '') : null;\n" + "var host = !isPageBlank ? window.location.hostname : null;\n" + "var port = !isPageBlank ? window.location.port : null;\n" + "if (_isOriginAllowed(allowedOriginRules, scheme, host, port)) {\n" + " window['" + + jsObjectNameEscaped + + "'] = new FlutterInAppWebViewWebMessageListener('" + + jsObjectNameEscaped + + "');\n" + "}\n" + "})();"; + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/window_id_js.h b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/window_id_js.h new file mode 100644 index 00000000..96699732 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/plugin_scripts_js/window_id_js.h @@ -0,0 +1,49 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_ID_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_ID_JS_H_ + +#include + +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin { + +/** + * JavaScript for managing window IDs in multi-window scenarios. + * + * This matches the iOS WindowIdJS.swift implementation. + * Window IDs are used to route JavaScript bridge calls to the correct + * webview when using window.open() or similar multi-window features. + * + * Note: The WINDOW_ID_VARIABLE_JS_SOURCE() function is now in JavaScriptBridgeJS + * to avoid circular dependencies. This class provides the initialization script. + */ +class WindowIdJS { + public: + inline static const std::string WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = + "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT"; + + /** + * Placeholder value that will be replaced with the actual window ID. + */ + inline static const std::string VAR_PLACEHOLDER_VALUE = "$PLACEHOLDER_VALUE"; + + /** + * Returns JavaScript code to initialize the window ID. + * The placeholder will be replaced with the actual window ID. + * Match iOS: WINDOW_ID_INITIALIZE_JS_SOURCE() + */ + static std::string WINDOW_ID_INITIALIZE_JS_SOURCE() { + return R"JS( +(function() { + )JS" + JavaScriptBridgeJS::WINDOW_ID_VARIABLE_JS_SOURCE() + + R"JS( = )JS" + VAR_PLACEHOLDER_VALUE + R"JS(; + return )JS" + + JavaScriptBridgeJS::WINDOW_ID_VARIABLE_JS_SOURCE() + R"JS(; +})() +)JS"; + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_ID_JS_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/proxy_manager.cc b/.vendor/flutter_inappwebview_linux/linux/proxy_manager.cc new file mode 100644 index 00000000..884799a5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/proxy_manager.cc @@ -0,0 +1,200 @@ +#include "proxy_manager.h" + +#include +#include + +#include "plugin_instance.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +// === ProxyRule === + +ProxyRule::ProxyRule(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + url = get_fl_map_value(map, "url", ""); + + // Check for schemeFilter - it may be a map with "rawValue" or a direct value + FlValue* schemeFilterValue = get_fl_map_value_raw(map, "schemeFilter"); + if (schemeFilterValue != nullptr) { + if (fl_value_get_type(schemeFilterValue) == FL_VALUE_TYPE_MAP) { + // It's a map, look for "rawValue" field + schemeFilter = get_optional_fl_map_value(schemeFilterValue, "rawValue"); + } else if (fl_value_get_type(schemeFilterValue) == FL_VALUE_TYPE_STRING) { + // It's a direct string + schemeFilter = std::string(fl_value_get_string(schemeFilterValue)); + } + } +} + +// === ProxySettings === + +ProxySettings::ProxySettings(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + bypassRules = get_fl_map_value>(map, "bypassRules", {}); + + // Parse proxyRules + FlValue* proxyRulesValue = get_fl_map_value_raw(map, "proxyRules"); + if (proxyRulesValue != nullptr && fl_value_get_type(proxyRulesValue) == FL_VALUE_TYPE_LIST) { + size_t length = fl_value_get_length(proxyRulesValue); + for (size_t i = 0; i < length; i++) { + FlValue* item = fl_value_get_list_value(proxyRulesValue, i); + if (item != nullptr && fl_value_get_type(item) == FL_VALUE_TYPE_MAP) { + proxyRules.emplace_back(item); + } + } + } +} + +// === ProxyManager === + +ProxyManager::ProxyManager(PluginInstance* plugin) + : ChannelDelegate(plugin->messenger(), METHOD_CHANNEL_NAME), + plugin_(plugin) {} + +ProxyManager::~ProxyManager() { + debugLog("dealloc ProxyManager"); + plugin_ = nullptr; +} + +void ProxyManager::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(method, "setProxyOverride")) { + FlValue* settingsMap = get_fl_map_value_raw(args, "settings"); + if (settingsMap == nullptr || fl_value_get_type(settingsMap) != FL_VALUE_TYPE_MAP) { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + return; + } + + ProxySettings settings(settingsMap); + setProxyOverride(settings); + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + + } else if (string_equals(method, "clearProxyOverride")) { + clearProxyOverride(); + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +void ProxyManager::setProxyOverride(const ProxySettings& settings) { + WebKitNetworkSession* session = webkit_network_session_get_default(); + if (session == nullptr) { + errorLog("ProxyManager: Failed to get default network session"); + return; + } + + // If no proxy rules, clear the proxy + if (settings.proxyRules.empty()) { + clearProxyOverride(); + return; + } + + // Build the ignore_hosts array from bypassRules + std::vector ignoreHostsCStrings; + for (const auto& rule : settings.bypassRules) { + ignoreHostsCStrings.push_back(rule.c_str()); + } + ignoreHostsCStrings.push_back(nullptr); // NULL-terminate the array + + // Get the default proxy URI (first proxy rule with no schemeFilter, or schemeFilter == "*") + std::string defaultProxyUri; + for (const auto& rule : settings.proxyRules) { + if (!rule.schemeFilter.has_value() || rule.schemeFilter.value().empty()) { + defaultProxyUri = rule.url; + break; + } + + std::string scheme = rule.schemeFilter.value(); + for (char& c : scheme) { + c = static_cast(std::tolower(static_cast(c))); + } + if (scheme == "*") { + defaultProxyUri = rule.url; + break; + } + } + + // Collect supported scheme-specific proxy rules so we can decide whether we + // need to force a direct default proxy to activate custom mode. + std::vector> schemeSpecificProxyRules; + schemeSpecificProxyRules.reserve(settings.proxyRules.size()); + for (const auto& rule : settings.proxyRules) { + if (!rule.schemeFilter.has_value() || rule.schemeFilter.value().empty()) { + continue; + } + + std::string scheme = rule.schemeFilter.value(); + for (char& c : scheme) { + c = static_cast(std::tolower(static_cast(c))); + } + + // Treat "*" as default proxy (already handled above). + if (scheme == "*") { + continue; + } + + // Accept common scheme filters. + if (scheme != "http" && scheme != "https" && scheme != "socks" && scheme != "socks4" && + scheme != "socks5") { + continue; + } + + schemeSpecificProxyRules.emplace_back(std::move(scheme), rule.url); + } + + // Create the proxy settings + WebKitNetworkProxySettings* proxySettings = webkit_network_proxy_settings_new( + defaultProxyUri.empty() ? nullptr : defaultProxyUri.c_str(), + settings.bypassRules.empty() ? nullptr : ignoreHostsCStrings.data()); + + if (proxySettings == nullptr) { + errorLog("ProxyManager: Failed to create WebKitNetworkProxySettings"); + return; + } + + // Add scheme-specific proxies + for (const auto& entry : schemeSpecificProxyRules) { + webkit_network_proxy_settings_add_proxy_for_scheme( + proxySettings, entry.first.c_str(), entry.second.c_str()); + } + + // Apply the proxy settings to the session + webkit_network_session_set_proxy_settings( + session, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxySettings); + + // Free the proxy settings + webkit_network_proxy_settings_free(proxySettings); +} + +void ProxyManager::clearProxyOverride() { + WebKitNetworkSession* session = webkit_network_session_get_default(); + if (session == nullptr) { + errorLog("ProxyManager: Failed to get default network session"); + return; + } + + // Revert to system default proxy settings + webkit_network_session_set_proxy_settings( + session, WEBKIT_NETWORK_PROXY_MODE_DEFAULT, nullptr); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/proxy_manager.h b/.vendor/flutter_inappwebview_linux/linux/proxy_manager.h new file mode 100644 index 00000000..7f0f9cd4 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/proxy_manager.h @@ -0,0 +1,76 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PROXY_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PROXY_MANAGER_H_ + +#include +#include + +#include +#include +#include +#include + +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/** + * Represents a proxy rule with URL and optional scheme filter. + */ +struct ProxyRule { + std::string url; + std::optional schemeFilter; // "HTTP", "HTTPS", or nullopt for all + + ProxyRule() = default; + ProxyRule(const std::string& url, std::optional schemeFilter = std::nullopt) + : url(url), schemeFilter(schemeFilter) {} + ProxyRule(FlValue* map); +}; + +/** + * Represents proxy settings configuration. + */ +struct ProxySettings { + std::vector bypassRules; + std::vector proxyRules; + + ProxySettings() = default; + ProxySettings(FlValue* map); +}; + +/** + * Manages proxy settings for WPE WebKit. + * Uses WebKitNetworkProxySettings and WebKitNetworkSession for proxy configuration. + */ +class ProxyManager : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_inappwebview_proxycontroller"; + + ProxyManager(PluginInstance* plugin); + ~ProxyManager() override; + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + void HandleMethodCall(FlMethodCall* method_call) override; + + /** + * Set proxy override with the given settings. + * This applies proxy settings to the default network session. + */ + void setProxyOverride(const ProxySettings& settings); + + /** + * Clear proxy override and revert to system defaults. + */ + void clearProxyOverride(); + + private: + PluginInstance* plugin_ = nullptr; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_PROXY_MANAGER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/test/flutter_inappwebview_linux_plugin_test.cc b/.vendor/flutter_inappwebview_linux/linux/test/flutter_inappwebview_linux_plugin_test.cc new file mode 100644 index 00000000..2971f7e7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/test/flutter_inappwebview_linux_plugin_test.cc @@ -0,0 +1,16 @@ +#include + +// Placeholder test for the flutter_inappwebview_linux plugin. +// More comprehensive tests can be added as features are implemented. + +namespace flutter_inappwebview_plugin { +namespace test { + +TEST(FlutterInappwebviewLinuxPlugin, Placeholder) { + // This is a placeholder test to satisfy the CMake build. + // Real tests should be added for the WebView functionality. + EXPECT_TRUE(true); +} + +} // namespace test +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/base_callback_result.h b/.vendor/flutter_inappwebview_linux/linux/types/base_callback_result.h new file mode 100644 index 00000000..d7718771 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/base_callback_result.h @@ -0,0 +1,111 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Base class for async callback results from Flutter method invocations. + * This mirrors the Windows BaseCallbackResult pattern, adapted for FlValue. + * + * Template parameter T is the decoded result type. + * + * Usage: + * 1. Create a subclass and set decodeResult in the constructor + * 2. Optionally set nonNullSuccess/nullSuccess to return whether default + * behaviour should run + * 3. Optionally set defaultBehaviour to run when no specific handling is done + * 4. Call handleResult() when the async response arrives + */ +template +class BaseCallbackResult { + public: + /** + * Error handler - called when an error response is received. + */ + std::function error; + + /** + * Not implemented handler - called when method is not implemented. + */ + std::function notImplemented; + + /** + * Called when result is non-null. + * Returns true if defaultBehaviour should run. + */ + std::function nonNullSuccess = [](const T) { return true; }; + + /** + * Called when result is null. + * Returns true if defaultBehaviour should run. + */ + std::function nullSuccess = []() { return true; }; + + /** + * Default behaviour to run after success handling. + */ + std::function result)> defaultBehaviour = [](const std::optional) { + }; + + /** + * Decode the FlValue result into type T. + * Must be set by subclasses. + */ + std::function(FlValue* result)> decodeResult = [](FlValue*) { + return std::nullopt; + }; + + BaseCallbackResult() = default; + virtual ~BaseCallbackResult() = default; + + /** + * Handle the async result from Flutter. + * This should be called from the GAsyncReadyCallback. + */ + void handleResult(FlValue* value) { + std::optional result = decodeResult ? decodeResult(value) : std::nullopt; + bool shouldRunDefaultBehaviour = false; + + if (result.has_value()) { + shouldRunDefaultBehaviour = + nonNullSuccess ? nonNullSuccess(result.value()) : shouldRunDefaultBehaviour; + } else { + shouldRunDefaultBehaviour = nullSuccess ? nullSuccess() : shouldRunDefaultBehaviour; + } + + if (shouldRunDefaultBehaviour && defaultBehaviour) { + defaultBehaviour(result); + } + } + + /** + * Handle an error result. + */ + void handleError(const std::string& code, const std::string& message) { + if (error) { + error(code, message); + } + } + + /** + * Handle not implemented response. + */ + void handleNotImplemented() { + if (defaultBehaviour) { + defaultBehaviour(std::nullopt); + } + if (notImplemented) { + notImplemented(); + } + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.cc b/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.cc new file mode 100644 index 00000000..7cb8d8b1 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.cc @@ -0,0 +1,60 @@ +#include "channel_delegate.h" + +#include + +namespace flutter_inappwebview_plugin { + +ChannelDelegate::ChannelDelegate(FlBinaryMessenger* messenger, const std::string& name) + : messenger_(messenger) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + channel_ = fl_method_channel_new(messenger_, name.c_str(), FL_METHOD_CODEC(codec)); + + fl_method_channel_set_method_call_handler(channel_, HandleMethodCallStatic, this, nullptr); +} + +ChannelDelegate::~ChannelDelegate() { + unregisterMethodCallHandler(); + if (channel_ != nullptr) { + g_object_unref(channel_); + channel_ = nullptr; + } + messenger_ = nullptr; +} + +void ChannelDelegate::HandleMethodCallStatic(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + auto* self = static_cast(user_data); + if (self) { + self->HandleMethodCall(method_call); + } +} + +void ChannelDelegate::HandleMethodCall(FlMethodCall* method_call) { + // Default implementation - subclasses should override + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void ChannelDelegate::invokeMethod(const std::string& method, FlValue* arguments) const { + if (channel_ == nullptr) { + return; + } + fl_method_channel_invoke_method(channel_, method.c_str(), arguments, nullptr, nullptr, nullptr); +} + +void ChannelDelegate::invokeMethodWithResult(const std::string& method, FlValue* arguments, + GAsyncReadyCallback callback, + gpointer user_data) const { + if (channel_ == nullptr) { + return; + } + fl_method_channel_invoke_method(channel_, method.c_str(), arguments, nullptr, callback, + user_data); +} + +void ChannelDelegate::unregisterMethodCallHandler() { + if (channel_ != nullptr) { + fl_method_channel_set_method_call_handler(channel_, nullptr, nullptr, nullptr); + } +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.h b/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.h new file mode 100644 index 00000000..94b41e24 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/channel_delegate.h @@ -0,0 +1,56 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Base class for channel delegates that handle Flutter method channels. + * This mirrors the Windows ChannelDelegate pattern. + */ +class ChannelDelegate { + public: + ChannelDelegate(FlBinaryMessenger* messenger, const std::string& name); + virtual ~ChannelDelegate(); + + /** + * Handle a method call from Flutter. + * Subclasses should override this to handle their specific methods. + */ + virtual void HandleMethodCall(FlMethodCall* method_call); + + /** + * Invoke a method on the Dart side without expecting a result. + */ + void invokeMethod(const std::string& method, FlValue* arguments) const; + + /** + * Invoke a method on the Dart side with a result callback. + */ + void invokeMethodWithResult(const std::string& method, FlValue* arguments, + GAsyncReadyCallback callback, gpointer user_data) const; + + /** + * Unregister the method call handler. + */ + void unregisterMethodCallHandler(); + + FlMethodChannel* channel() const { return channel_; } + FlBinaryMessenger* messenger() const { return messenger_; } + + protected: + FlBinaryMessenger* messenger_ = nullptr; + FlMethodChannel* channel_ = nullptr; + + private: + static void HandleMethodCallStatic(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.cc b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.cc new file mode 100644 index 00000000..c38cec1e --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.cc @@ -0,0 +1,45 @@ +#include "client_cert_challenge.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +ClientCertChallenge::ClientCertChallenge(const URLProtectionSpace& protectionSpace, bool isProxy) + : protectionSpace(protectionSpace), isProxy(isProxy) {} + +FlValue* ClientCertChallenge::toFlValue() const { + // Convert keyTypes to FlValue list + g_autoptr(FlValue) keyTypesValue = fl_value_new_list(); + for (const auto& keyType : keyTypes) { + fl_value_append_take(keyTypesValue, fl_value_new_string(keyType.c_str())); + } + + // Convert principals to FlValue list + g_autoptr(FlValue) principalsValue = fl_value_new_list(); + for (const auto& principal : principals) { + fl_value_append_take(principalsValue, fl_value_new_string(principal.c_str())); + } + + // Convert allowedCertificateAuthorities to FlValue list + g_autoptr(FlValue) allowedCAsValue = fl_value_new_list(); + for (const auto& ca : allowedCertificateAuthorities) { + fl_value_append_take(allowedCAsValue, fl_value_new_string(ca.c_str())); + } + + // Convert mutuallyTrustedCertificates to FlValue list + g_autoptr(FlValue) trustedCertsValue = fl_value_new_list(); + for (const auto& cert : mutuallyTrustedCertificates) { + fl_value_append(trustedCertsValue, cert.toFlValue()); + } + + return to_fl_map({ + {"protectionSpace", protectionSpace.toFlValue()}, + {"isProxy", make_fl_value(isProxy)}, + {"keyTypes", fl_value_ref(keyTypesValue)}, + {"principals", fl_value_ref(principalsValue)}, + {"allowedCertificateAuthorities", fl_value_ref(allowedCAsValue)}, + {"mutuallyTrustedCertificates", fl_value_ref(trustedCertsValue)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.h b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.h new file mode 100644 index 00000000..2043be3c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_challenge.h @@ -0,0 +1,52 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ + +#include + +#include +#include +#include + +#include "url_protection_space.h" +#include "ssl_certificate.h" + +namespace flutter_inappwebview_plugin { + +/** + * Client certificate challenge - sent when server requests a client certificate. + * + * This is used for mTLS (mutual TLS) authentication scenarios. + * + * NOTE: WPE WebKit's support for providing client certificates programmatically + * is limited. The `webkit_credential_new_for_certificate()` API (since 2.34) + * allows providing a certificate, but this may not be available on all systems. + * When not available, the app can at least be notified of the request. + */ +class ClientCertChallenge { + public: + URLProtectionSpace protectionSpace; + + // Whether this is a proxy authentication request + bool isProxy; + + // Key types accepted by the server (if available) + std::vector keyTypes; + + // Certificate authorities accepted by the server (Base64 DER encoded, if available) + std::vector principals; + + // Allowed certificate authorities (Base64 encoded, for Windows compatibility) + std::vector allowedCertificateAuthorities; + + // Mutually trusted certificates (if available) + std::vector mutuallyTrustedCertificates; + + ClientCertChallenge(const URLProtectionSpace& protectionSpace, bool isProxy); + ~ClientCertChallenge() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.cc new file mode 100644 index 00000000..0f35225b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.cc @@ -0,0 +1,62 @@ +#include "client_cert_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +ClientCertResponse::ClientCertResponse() + : action(ClientCertResponseAction::CANCEL), selectedCertificate(-1) {} + +ClientCertResponse::ClientCertResponse(FlValue* map) + : action(ClientCertResponseAction::CANCEL), selectedCertificate(-1) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + // Parse action + FlValue* actionValue = fl_value_lookup_string(map, "action"); + if (actionValue != nullptr && fl_value_get_type(actionValue) == FL_VALUE_TYPE_INT) { + int actionInt = static_cast(fl_value_get_int(actionValue)); + switch (actionInt) { + case 0: + action = ClientCertResponseAction::CANCEL; + break; + case 1: + action = ClientCertResponseAction::PROCEED; + break; + case 2: + action = ClientCertResponseAction::IGNORE; + break; + default: + action = ClientCertResponseAction::CANCEL; + break; + } + } + + // Parse certificatePath + certificatePath = get_optional_fl_map_value(map, "certificatePath"); + + // Parse certificatePassword + certificatePassword = get_optional_fl_map_value(map, "certificatePassword"); + + // Parse keyStoreType + keyStoreType = get_optional_fl_map_value(map, "keyStoreType"); + + // Parse selectedCertificate + FlValue* selectedValue = fl_value_lookup_string(map, "selectedCertificate"); + if (selectedValue != nullptr && fl_value_get_type(selectedValue) == FL_VALUE_TYPE_INT) { + selectedCertificate = static_cast(fl_value_get_int(selectedValue)); + } +} + +std::optional ClientCertResponse::fromFlValue(FlValue* value) { + if (value == nullptr || fl_value_get_type(value) == FL_VALUE_TYPE_NULL) { + return std::nullopt; + } + if (fl_value_get_type(value) == FL_VALUE_TYPE_MAP) { + return ClientCertResponse(value); + } + return std::nullopt; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.h b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.h new file mode 100644 index 00000000..34ca8ebd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/client_cert_response.h @@ -0,0 +1,53 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Action to take in response to a client certificate request. + */ +enum class ClientCertResponseAction { + CANCEL = 0, // Cancel the request + PROCEED = 1, // Proceed with a certificate + IGNORE = 2, // Ignore the request (for now) +}; + +/** + * Response to a client certificate challenge. + * + * NOTE: WPE WebKit's ability to programmatically provide a client certificate + * is limited. The webkit_credential_new_for_certificate() API requires a + * GTlsCertificate which must be loaded from a file. If the certificate cannot + * be loaded or the API is not available, PROCEED will behave like CANCEL. + */ +class ClientCertResponse { + public: + ClientCertResponseAction action; + + // Path to the certificate file (PEM or PKCS12 format) + std::optional certificatePath; + + // Password for the certificate (if PKCS12) + std::optional certificatePassword; + + // Key store type (e.g., "PKCS12", "PEM") + std::optional keyStoreType; + + // Index of selected certificate (for Windows compatibility, not used on Linux) + int selectedCertificate; + + ClientCertResponse(); + explicit ClientCertResponse(FlValue* map); + ~ClientCertResponse() = default; + + static std::optional fromFlValue(FlValue* value); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/content_world.cc b/.vendor/flutter_inappwebview_linux/linux/types/content_world.cc new file mode 100644 index 00000000..524d5a09 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/content_world.cc @@ -0,0 +1,50 @@ +#include "content_world.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +// Static member initialization +std::shared_ptr ContentWorld::pageWorld_; +std::shared_ptr ContentWorld::defaultClientWorld_; + +ContentWorld::ContentWorld(const std::string& name) : name(name) {} + +ContentWorld::ContentWorld(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + name = "page"; + return; + } + + name = get_fl_map_value(map, "name", "page"); +} + +FlValue* ContentWorld::toFlValue() const { + return to_fl_map({ + {"name", make_fl_value(name)}, + }); +} + +bool ContentWorld::operator==(const ContentWorld& other) const { + return name == other.name; +} + +std::shared_ptr ContentWorld::page() { + if (!pageWorld_) { + pageWorld_ = std::make_shared("page"); + } + return pageWorld_; +} + +std::shared_ptr ContentWorld::defaultClient() { + if (!defaultClientWorld_) { + defaultClientWorld_ = std::make_shared("defaultClient"); + } + return defaultClientWorld_; +} + +std::shared_ptr ContentWorld::world(const std::string& name) { + return std::make_shared(name); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/content_world.h b/.vendor/flutter_inappwebview_linux/linux/types/content_world.h new file mode 100644 index 00000000..0029101b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/content_world.h @@ -0,0 +1,40 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Represents a content world for JavaScript execution isolation. + * WebKitGTK doesn't have full content world isolation like WKWebView, + * but we can track the concept for API compatibility. + */ +class ContentWorld { + public: + std::string name; + + ContentWorld(const std::string& name); + ContentWorld(FlValue* map); + + FlValue* toFlValue() const; + + bool operator==(const ContentWorld& other) const; + + // Factory methods for well-known content worlds + static std::shared_ptr page(); + static std::shared_ptr defaultClient(); + static std::shared_ptr world(const std::string& name); + + private: + static std::shared_ptr pageWorld_; + static std::shared_ptr defaultClientWorld_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/context_menu.h b/.vendor/flutter_inappwebview_linux/linux/types/context_menu.h new file mode 100644 index 00000000..c757bfd9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/context_menu.h @@ -0,0 +1,120 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_H_ + +#include + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Context menu settings - matches Dart ContextMenuSettings class. + */ +class ContextMenuSettings { + public: + /// Whether all the default system context menu items should be hidden or not. + bool hideDefaultSystemContextMenuItems = false; + + ContextMenuSettings() = default; + + explicit ContextMenuSettings(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + FlValue* hideDefault = fl_value_lookup_string(map, "hideDefaultSystemContextMenuItems"); + if (hideDefault != nullptr && fl_value_get_type(hideDefault) == FL_VALUE_TYPE_BOOL) { + hideDefaultSystemContextMenuItems = fl_value_get_bool(hideDefault); + } + } +}; + +/** + * Context menu item - matches Dart ContextMenuItem class. + */ +class ContextMenuItem { + public: + /// Menu item ID - can be a string or int + std::variant id; + + /// Menu item title + std::string title; + + ContextMenuItem() = default; + + explicit ContextMenuItem(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + // Parse id - can be string or int + FlValue* idValue = fl_value_lookup_string(map, "id"); + if (idValue != nullptr) { + if (fl_value_get_type(idValue) == FL_VALUE_TYPE_STRING) { + id = std::string(fl_value_get_string(idValue)); + } else if (fl_value_get_type(idValue) == FL_VALUE_TYPE_INT) { + id = fl_value_get_int(idValue); + } + } + + // Parse title + FlValue* titleValue = fl_value_lookup_string(map, "title"); + if (titleValue != nullptr && fl_value_get_type(titleValue) == FL_VALUE_TYPE_STRING) { + title = std::string(fl_value_get_string(titleValue)); + } + } + + /// Get the ID as a string for comparison/storage + std::string getIdAsString() const { + if (std::holds_alternative(id)) { + return std::get(id); + } else { + return std::to_string(std::get(id)); + } + } +}; + +/** + * Context menu configuration - matches Dart ContextMenu class. + */ +class ContextMenu { + public: + /// Context menu settings + ContextMenuSettings settings; + + /// List of custom menu items + std::vector menuItems; + + ContextMenu() = default; + + explicit ContextMenu(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + // Parse settings + FlValue* settingsValue = fl_value_lookup_string(map, "settings"); + if (settingsValue != nullptr && fl_value_get_type(settingsValue) == FL_VALUE_TYPE_MAP) { + settings = ContextMenuSettings(settingsValue); + } + + // Parse menu items + FlValue* menuItemsValue = fl_value_lookup_string(map, "menuItems"); + if (menuItemsValue != nullptr && fl_value_get_type(menuItemsValue) == FL_VALUE_TYPE_LIST) { + size_t length = fl_value_get_length(menuItemsValue); + for (size_t i = 0; i < length; ++i) { + FlValue* item = fl_value_get_list_value(menuItemsValue, i); + if (item != nullptr && fl_value_get_type(item) == FL_VALUE_TYPE_MAP) { + menuItems.emplace_back(item); + } + } + } + } +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.cc b/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.cc new file mode 100644 index 00000000..09490374 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.cc @@ -0,0 +1,490 @@ +#include "context_menu_popup.h" + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace flutter_inappwebview_plugin { + +ContextMenuPopup::ContextMenuPopup(GtkWindow* parent_window) : parent_window_(parent_window) { + // Create a popup window + popup_window_ = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_type_hint(GTK_WINDOW(popup_window_), GDK_WINDOW_TYPE_HINT_POPUP_MENU); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(popup_window_), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(popup_window_), TRUE); + gtk_window_set_decorated(GTK_WINDOW(popup_window_), FALSE); + gtk_window_set_resizable(GTK_WINDOW(popup_window_), FALSE); + + if (parent_window_ != nullptr) { + gtk_window_set_transient_for(GTK_WINDOW(popup_window_), parent_window_); + } + + // Enable RGBA for transparency + GdkScreen* screen = gtk_widget_get_screen(popup_window_); + GdkVisual* visual = gdk_screen_get_rgba_visual(screen); + if (visual != nullptr) { + gtk_widget_set_visual(popup_window_, visual); + } + gtk_widget_set_app_paintable(popup_window_, TRUE); + + // Create drawing area + drawing_area_ = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(popup_window_), drawing_area_); + + // Enable events on drawing area + gtk_widget_add_events(drawing_area_, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_LEAVE_NOTIFY_MASK); + + // Connect signals for drawing area + g_signal_connect(drawing_area_, "draw", G_CALLBACK(OnDraw), this); + g_signal_connect(drawing_area_, "button-press-event", G_CALLBACK(OnButtonPress), this); + g_signal_connect(drawing_area_, "button-release-event", G_CALLBACK(OnButtonRelease), this); + g_signal_connect(drawing_area_, "motion-notify-event", G_CALLBACK(OnMotionNotify), this); + g_signal_connect(drawing_area_, "leave-notify-event", G_CALLBACK(OnLeaveNotify), this); + + // Also connect button-press to popup window for when clicks land on window edge + g_signal_connect(popup_window_, "button-press-event", G_CALLBACK(OnButtonPress), this); + + // Connect signals for popup window + g_signal_connect(popup_window_, "focus-out-event", G_CALLBACK(OnFocusOut), this); + g_signal_connect(popup_window_, "unrealize", G_CALLBACK(OnUnrealize), this); + + gtk_widget_show(drawing_area_); +} + +ContextMenuPopup::~ContextMenuPopup() { + Hide(); // Ensure cleanup + if (popup_window_ != nullptr) { + gtk_widget_destroy(popup_window_); + popup_window_ = nullptr; + } +} + +void ContextMenuPopup::AddItem(const std::string& id, const std::string& title, bool enabled) { + PopupMenuItem item; + item.id = id; + item.title = title; + item.enabled = enabled; + item.is_separator = false; + items_.push_back(std::move(item)); +} + +void ContextMenuPopup::AddSeparator() { + PopupMenuItem item; + item.is_separator = true; + items_.push_back(std::move(item)); +} + +void ContextMenuPopup::Clear() { + items_.clear(); + hovered_index_ = -1; + pressed_index_ = -1; +} + +void ContextMenuPopup::UpdateSize() { + if (items_.empty()) { + width_ = MENU_MIN_WIDTH; + height_ = MENU_VERTICAL_PADDING * 2; + return; + } + + // Calculate width based on text + cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + cairo_t* cr = cairo_create(surface); + + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, MENU_ITEM_TEXT_SIZE); + + int max_text_width = 0; + int non_separator_count = 0; + for (const auto& item : items_) { + if (!item.is_separator) { + non_separator_count++; + cairo_text_extents_t extents; + cairo_text_extents(cr, item.title.c_str(), &extents); + max_text_width = std::max(max_text_width, static_cast(extents.width)); + } + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + width_ = std::max(MENU_MIN_WIDTH, std::min(MENU_MAX_WIDTH, max_text_width + MENU_HORIZONTAL_PADDING * 4)); + + // Calculate height with separator filtering logic + height_ = MENU_VERTICAL_PADDING * 2; + bool last_was_separator = true; // Start true to skip leading separators + + for (size_t i = 0; i < items_.size(); ++i) { + const auto& item = items_[i]; + + if (item.is_separator) { + // Skip separator if: + // 1. Only 1 or fewer non-separator items + // 2. Last item was a separator (consecutive) + // 3. This is at the start + // 4. This is at the end (all remaining items are separators) + if (non_separator_count <= 1 || last_was_separator) { + continue; + } + + // Check if trailing separator + bool is_trailing = true; + for (size_t j = i + 1; j < items_.size(); ++j) { + if (!items_[j].is_separator) { + is_trailing = false; + break; + } + } + if (is_trailing) { + continue; + } + + height_ += MENU_SEPARATOR_HEIGHT; + last_was_separator = true; + } else { + height_ += MENU_ITEM_HEIGHT; + last_was_separator = false; + } + } +} + +void ContextMenuPopup::Show(int x, int y) { + if (items_.empty()) { + return; + } + + UpdateSize(); + + // Get display for monitor geometry + GdkDisplay* display = gdk_display_get_default(); + + // Get screen/monitor geometry for boundary checks + GdkMonitor* monitor = nullptr; + int screen_x = 0, screen_y = 0; + int screen_width = 1920, screen_height = 1080; + + if (display != nullptr) { + monitor = gdk_display_get_monitor_at_point(display, x, y); + if (monitor == nullptr) { + monitor = gdk_display_get_primary_monitor(display); + } + if (monitor == nullptr) { + monitor = gdk_display_get_monitor(display, 0); + } + } + + if (monitor != nullptr) { + GdkRectangle geometry; + gdk_monitor_get_geometry(monitor, &geometry); + screen_x = geometry.x; + screen_y = geometry.y; + screen_width = geometry.x + geometry.width; + screen_height = geometry.y + geometry.height; + } + + // Position menu so its top-left corner is at (x, y), adjusted for screen bounds + // If it would go off the right edge, flip to show on the left of cursor + if (x + width_ > screen_width) { + x = x - width_; + } + // If it would go off the bottom, flip to show above cursor + if (y + height_ > screen_height) { + y = y - height_; + } + // Ensure it stays within screen bounds + if (x < screen_x) { + x = screen_x; + } + if (y < screen_y) { + y = screen_y; + } + + // Store menu position for outside click detection + menu_x_ = x; + menu_y_ = y; + + gtk_widget_set_size_request(drawing_area_, width_, height_); + gtk_window_resize(GTK_WINDOW(popup_window_), width_, height_); + gtk_window_move(GTK_WINDOW(popup_window_), x, y); + gtk_window_set_position(GTK_WINDOW(popup_window_), GTK_WIN_POS_NONE); + gtk_widget_show(popup_window_); + gtk_window_move(GTK_WINDOW(popup_window_), x, y); + + visible_ = true; + + // Connect to parent window as fallback for clicks outside Flutter area + if (parent_window_ != nullptr && parent_button_handler_id_ == 0) { + parent_button_handler_id_ = g_signal_connect( + parent_window_, "button-press-event", G_CALLBACK(OnParentButtonPress), this); + } +} + +void ContextMenuPopup::Hide() { + if (!visible_) { + return; + } + + visible_ = false; + hovered_index_ = -1; + pressed_index_ = -1; + + // Disconnect from parent window + if (parent_window_ != nullptr && parent_button_handler_id_ != 0) { + g_signal_handler_disconnect(parent_window_, parent_button_handler_id_); + parent_button_handler_id_ = 0; + } + + // Hide the popup + gtk_widget_hide(popup_window_); + + if (dismissed_callback_) { + dismissed_callback_(); + } +} + +int ContextMenuPopup::GetItemAtPosition(int x, int y) const { + if (x < 0 || x >= width_) { + return -1; + } + + int current_y = MENU_VERTICAL_PADDING; + for (size_t i = 0; i < items_.size(); ++i) { + int item_height = items_[i].is_separator ? MENU_SEPARATOR_HEIGHT : MENU_ITEM_HEIGHT; + + if (y >= current_y && y < current_y + item_height) { + if (items_[i].is_separator || !items_[i].enabled) { + return -1; // Can't select separators or disabled items + } + return static_cast(i); + } + + current_y += item_height; + } + + return -1; +} + +void ContextMenuPopup::Paint() { + gtk_widget_queue_draw(drawing_area_); +} + +gboolean ContextMenuPopup::OnDraw(GtkWidget* widget, cairo_t* cr, gpointer user_data) { + auto* self = static_cast(user_data); + + // Clear with transparency + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + // Draw background with rounded corners + double radius = 6.0; + double x = 0, y = 0; + double w = self->width_, h = self->height_; + + cairo_new_path(cr); + cairo_arc(cr, x + w - radius, y + radius, radius, -M_PI / 2, 0); + cairo_arc(cr, x + w - radius, y + h - radius, radius, 0, M_PI / 2); + cairo_arc(cr, x + radius, y + h - radius, radius, M_PI / 2, M_PI); + cairo_arc(cr, x + radius, y + radius, radius, M_PI, 3 * M_PI / 2); + cairo_close_path(cr); + + // Background + cairo_set_source_rgba(cr, 0.98, 0.98, 0.98, 0.98); + cairo_fill_preserve(cr); + + // Border + cairo_set_source_rgba(cr, 0.7, 0.7, 0.7, 1.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + + // Draw items + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, MENU_ITEM_TEXT_SIZE); + + // Count non-separator items + int non_separator_count = 0; + for (const auto& item : self->items_) { + if (!item.is_separator) { + non_separator_count++; + } + } + + int current_y = MENU_VERTICAL_PADDING; + bool last_was_separator = true; // Start true to skip leading separators + + for (size_t i = 0; i < self->items_.size(); ++i) { + const auto& item = self->items_[i]; + + if (item.is_separator) { + // Skip separator if: + // 1. Only 1 or fewer non-separator items + // 2. Last drawn item was a separator (consecutive) + // 3. This is at the start (last_was_separator is true initially) + // 4. This is at the end (check if remaining items are all separators) + if (non_separator_count <= 1 || last_was_separator) { + continue; + } + + // Check if this is a trailing separator (all remaining items are separators) + bool is_trailing = true; + for (size_t j = i + 1; j < self->items_.size(); ++j) { + if (!self->items_[j].is_separator) { + is_trailing = false; + break; + } + } + if (is_trailing) { + continue; + } + + // Draw separator line + int sep_y = current_y + MENU_SEPARATOR_HEIGHT / 2; + cairo_set_source_rgba(cr, 0.8, 0.8, 0.8, 1.0); + cairo_set_line_width(cr, 1.0); + cairo_move_to(cr, MENU_HORIZONTAL_PADDING, sep_y); + cairo_line_to(cr, self->width_ - MENU_HORIZONTAL_PADDING, sep_y); + cairo_stroke(cr); + + current_y += MENU_SEPARATOR_HEIGHT; + last_was_separator = true; + } else { + // Draw item background if hovered + if (static_cast(i) == self->hovered_index_ && item.enabled) { + cairo_set_source_rgba(cr, 0.2, 0.5, 0.9, 1.0); + cairo_rectangle(cr, 4, current_y, self->width_ - 8, MENU_ITEM_HEIGHT); + cairo_fill(cr); + } + + // Draw text + if (!item.enabled) { + cairo_set_source_rgba(cr, 0.6, 0.6, 0.6, 1.0); + } else if (static_cast(i) == self->hovered_index_) { + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + } else { + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + } + + cairo_move_to(cr, MENU_HORIZONTAL_PADDING, current_y + MENU_ITEM_HEIGHT - 10); + cairo_show_text(cr, item.title.c_str()); + + current_y += MENU_ITEM_HEIGHT; + last_was_separator = false; + } + } + + return TRUE; +} + +gboolean ContextMenuPopup::OnButtonPress(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + auto* self = static_cast(user_data); + + // Get screen coordinates of the click + gint screen_x = static_cast(event->x_root); + gint screen_y = static_cast(event->y_root); + + // Check if click is outside the menu bounds + bool outside = (screen_x < self->menu_x_ || screen_x >= self->menu_x_ + self->width_ || + screen_y < self->menu_y_ || screen_y >= self->menu_y_ + self->height_); + + if (outside) { + // Click was outside the menu - dismiss it + self->Hide(); + return FALSE; // Let the event propagate + } + + if (event->button == 1) { + int index = self->GetItemAtPosition(static_cast(event->x), static_cast(event->y)); + self->pressed_index_ = index; + } else if (event->button == 3) { + // Right click outside menu item dismisses + self->Hide(); + return TRUE; + } + + return TRUE; +} + +gboolean ContextMenuPopup::OnButtonRelease(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + auto* self = static_cast(user_data); + + if (event->button == 1) { + int index = self->GetItemAtPosition(static_cast(event->x), static_cast(event->y)); + + if (index >= 0 && index == self->pressed_index_ && index < static_cast(self->items_.size())) { + const auto& item = self->items_[index]; + if (item.enabled && !item.is_separator) { + std::string id = item.id; + std::string title = item.title; + + self->Hide(); + + if (self->item_callback_) { + self->item_callback_(id, title); + } + } + } + } else if (event->button == 3) { + // Right click dismisses menu + self->Hide(); + } + + self->pressed_index_ = -1; + return TRUE; +} + +gboolean ContextMenuPopup::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) { + auto* self = static_cast(user_data); + + int index = self->GetItemAtPosition(static_cast(event->x), static_cast(event->y)); + + if (index != self->hovered_index_) { + self->hovered_index_ = index; + self->Paint(); + } + + return TRUE; +} + +gboolean ContextMenuPopup::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, + gpointer user_data) { + auto* self = static_cast(user_data); + + if (self->hovered_index_ >= 0) { + self->hovered_index_ = -1; + self->Paint(); + } + + return TRUE; +} + +gboolean ContextMenuPopup::OnParentButtonPress(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + auto* self = static_cast(user_data); + // Any click on parent window while popup is visible should hide the popup + if (self->visible_) { + self->Hide(); + } + return FALSE; // Let the event continue to Flutter +} + +gboolean ContextMenuPopup::OnFocusOut(GtkWidget* widget, GdkEventFocus* event, gpointer user_data) { + auto* self = static_cast(user_data); + if (self->visible_) { + self->Hide(); + } + return FALSE; +} + +void ContextMenuPopup::OnUnrealize(GtkWidget* widget, gpointer user_data) { + auto* self = static_cast(user_data); + self->visible_ = false; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.h b/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.h new file mode 100644 index 00000000..99d8f454 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/context_menu_popup.h @@ -0,0 +1,99 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_POPUP_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_POPUP_H_ + +#include +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +// Constants for menu rendering +constexpr int MENU_VERTICAL_PADDING = 8; +constexpr int MENU_HORIZONTAL_PADDING = 12; +constexpr int MENU_ITEM_HEIGHT = 32; +constexpr int MENU_ITEM_TEXT_SIZE = 14; +constexpr int MENU_SEPARATOR_HEIGHT = 9; +constexpr int MENU_MIN_WIDTH = 150; +constexpr int MENU_MAX_WIDTH = 400; + +struct PopupMenuItem { + std::string id; + std::string title; + bool enabled = true; + bool is_separator = false; +}; + +// Callback when a menu item is clicked +using MenuItemCallback = std::function; +// Callback when the menu is dismissed +using MenuDismissedCallback = std::function; + +class ContextMenuPopup { + public: + ContextMenuPopup(GtkWindow* parent_window); + ~ContextMenuPopup(); + + // Add a menu item + void AddItem(const std::string& id, const std::string& title, bool enabled = true); + + // Add a separator + void AddSeparator(); + + // Clear all items + void Clear(); + + // Show the popup at the given screen coordinates + void Show(int x, int y); + + // Hide the popup + void Hide(); + + // Check if the popup is visible + bool IsVisible() const { return visible_; } + + // Set callbacks + void SetItemCallback(MenuItemCallback callback) { item_callback_ = std::move(callback); } + void SetDismissedCallback(MenuDismissedCallback callback) { + dismissed_callback_ = std::move(callback); + } + + private: + // Cairo drawing + void Paint(); + void UpdateSize(); + int GetItemAtPosition(int x, int y) const; + + // GTK signal handlers + static gboolean OnDraw(GtkWidget* widget, cairo_t* cr, gpointer user_data); + static gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static gboolean OnButtonRelease(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, gpointer user_data); + static gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, gpointer user_data); + static gboolean OnParentButtonPress(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static gboolean OnFocusOut(GtkWidget* widget, GdkEventFocus* event, gpointer user_data); + static void OnUnrealize(GtkWidget* widget, gpointer user_data); + + GtkWindow* parent_window_ = nullptr; + GtkWidget* popup_window_ = nullptr; + GtkWidget* drawing_area_ = nullptr; + + std::vector items_; + int width_ = 0; + int height_ = 0; + int menu_x_ = 0; // Screen X position of menu + int menu_y_ = 0; // Screen Y position of menu + int hovered_index_ = -1; + int pressed_index_ = -1; + bool visible_ = false; + gulong parent_button_handler_id_ = 0; + + MenuItemCallback item_callback_; + MenuDismissedCallback dismissed_callback_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CONTEXT_MENU_POPUP_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.cc b/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.cc new file mode 100644 index 00000000..17844f05 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.cc @@ -0,0 +1,87 @@ +#include "create_window_action.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +WindowFeatures::WindowFeatures(WebKitWindowProperties* properties) { + if (properties == nullptr) { + return; + } + + // WPE WebKit doesn't have window geometry API - use defaults + // WPE is headless so window properties aren't meaningful + menuBarVisible = false; + statusBarVisible = false; + toolbarsVisible = false; + scrollbarsVisible = true; + locationbarVisible = false; + fullscreen = false; + resizable = true; +} + +FlValue* WindowFeatures::toFlValue() const { + return to_fl_map({ + {"menuBarVisible", make_fl_value(menuBarVisible)}, + {"statusBarVisible", make_fl_value(statusBarVisible)}, + {"toolbarsVisible", make_fl_value(toolbarsVisible)}, + {"scrollbarsVisible", make_fl_value(scrollbarsVisible)}, + {"locationbarVisible", make_fl_value(locationbarVisible)}, + {"fullscreen", make_fl_value(fullscreen)}, + {"resizable", make_fl_value(resizable)}, + {"x", make_fl_value(x)}, + {"y", make_fl_value(y)}, + {"width", make_fl_value(width)}, + {"height", make_fl_value(height)}, + }); +} + +CreateWindowAction::CreateWindowAction(WebKitNavigationAction* navigationAction, int64_t windowId, + WebKitWindowProperties* windowProperties) + : windowId(windowId), isUserGesture(false), isForMainFrame(true) { + if (navigationAction != nullptr) { + WebKitURIRequest* uriRequest = webkit_navigation_action_get_request(navigationAction); + if (uriRequest != nullptr) { + const gchar* uri = webkit_uri_request_get_uri(uriRequest); + const gchar* method = webkit_uri_request_get_http_method(uriRequest); + + request = std::make_shared( + uri != nullptr ? std::make_optional(std::string(uri)) : std::nullopt, + method != nullptr ? std::make_optional(std::string(method)) + : std::make_optional(std::string("GET")), + std::nullopt, // headers + std::nullopt // body + ); + } + + navigationType = + static_cast(webkit_navigation_action_get_navigation_type(navigationAction)); + isUserGesture = webkit_navigation_action_is_user_gesture(navigationAction); + + // Get target frame name + const gchar* frameName = webkit_navigation_action_get_frame_name(navigationAction); + if (frameName != nullptr) { + targetFrame = std::string(frameName); + } + } + + if (windowProperties != nullptr) { + windowFeatures = WindowFeatures(windowProperties); + } +} + +FlValue* CreateWindowAction::toFlValue() const { + return to_fl_map({ + {"windowId", make_fl_value(windowId)}, + {"isDialog", make_fl_value(isDialog)}, + {"request", request ? request->toFlValue() : make_fl_value()}, + {"navigationType", make_fl_value(navigationType)}, + {"isForMainFrame", make_fl_value(isForMainFrame)}, + {"hasGesture", make_fl_value(isUserGesture)}, + {"targetFrame", make_fl_value(targetFrame)}, + {"sourceUrl", make_fl_value(sourceUrl)}, + {"windowFeatures", windowFeatures.has_value() ? windowFeatures->toFlValue() : make_fl_value()}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.h b/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.h new file mode 100644 index 00000000..2d756b45 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/create_window_action.h @@ -0,0 +1,81 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ + +#include +#include + +#include +#include +#include +#include + +#include "url_request.h" + +namespace flutter_inappwebview_plugin { + +/** + * Represents window features from WebKitWindowProperties. + */ +class WindowFeatures { + public: + std::optional menuBarVisible; + std::optional statusBarVisible; + std::optional toolbarsVisible; + std::optional scrollbarsVisible; + std::optional locationbarVisible; + std::optional fullscreen; + std::optional resizable; + std::optional x; + std::optional y; + std::optional width; + std::optional height; + + WindowFeatures() = default; + WindowFeatures(WebKitWindowProperties* properties); + ~WindowFeatures() = default; + + FlValue* toFlValue() const; +}; + +/** + * Represents an action to create a new window (e.g., from window.open() or target="_blank"). + */ +class CreateWindowAction { + public: + // The window id assigned to the new window + int64_t windowId; + + // Whether this is a dialog window + std::optional isDialog; + + // The navigation action that triggered this + std::shared_ptr request; + + // Navigation type (same as NavigationAction) + int64_t navigationType; + + // Whether the navigation was triggered by a user gesture + bool isUserGesture; + + // Whether it's for the main frame + bool isForMainFrame; + + // Target frame name (e.g., "_blank") + std::optional targetFrame; + + // Source URL (the URL of the page that initiated the request) + std::optional sourceUrl; + + // Window features + std::optional windowFeatures; + + CreateWindowAction(WebKitNavigationAction* navigationAction, int64_t windowId, + WebKitWindowProperties* windowProperties = nullptr); + ~CreateWindowAction() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.cc new file mode 100644 index 00000000..fdfcbed2 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.cc @@ -0,0 +1,28 @@ +#include "custom_scheme_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +CustomSchemeResponse::CustomSchemeResponse() + : contentType("application/octet-stream"), contentEncoding("utf-8") {} + +CustomSchemeResponse::CustomSchemeResponse(FlValue* map) + : contentType(get_fl_map_value(map, "contentType", "application/octet-stream")), + contentEncoding(get_fl_map_value(map, "contentEncoding", "utf-8")) { + // Parse data (Uint8List) - needs special handling for byte arrays + auto data_opt = get_optional_fl_map_value>(map, "data"); + if (data_opt.has_value()) { + data = data_opt.value(); + } +} + +FlValue* CustomSchemeResponse::toFlValue() const { + return to_fl_map({ + {"data", make_fl_value(data)}, + {"contentType", make_fl_value(contentType)}, + {"contentEncoding", make_fl_value(contentEncoding)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.h b/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.h new file mode 100644 index 00000000..ea8bfb0b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/custom_scheme_response.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * CustomSchemeResponse - Response for custom URI scheme requests. + * + * This class represents the response returned by the onLoadResourceWithCustomScheme + * event. It allows loading a specific resource with custom scheme. + */ +class CustomSchemeResponse { + public: + // Data to be returned for the custom scheme request + std::vector data; + + // Content-Type of the data, such as "image/png" + std::string contentType; + + // Content-Encoding of the data, such as "utf-8" + std::string contentEncoding; + + CustomSchemeResponse(); + explicit CustomSchemeResponse(FlValue* map); + ~CustomSchemeResponse() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.cc new file mode 100644 index 00000000..e9af4b72 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.cc @@ -0,0 +1,68 @@ +#include "download_start_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +// === DownloadStartRequest === + +DownloadStartRequest::DownloadStartRequest() : contentLength(-1) {} + +DownloadStartRequest::DownloadStartRequest(WebKitDownload* download) : contentLength(-1) { + if (download == nullptr) { + return; + } + + WebKitURIRequest* request = webkit_download_get_request(download); + if (request != nullptr) { + const gchar* uri = webkit_uri_request_get_uri(request); + if (uri != nullptr) { + url = std::string(uri); + } + } + + WebKitURIResponse* response = webkit_download_get_response(download); + if (response != nullptr) { + const gchar* mimeTypeStr = webkit_uri_response_get_mime_type(response); + if (mimeTypeStr != nullptr) { + mimeType = std::string(mimeTypeStr); + } + + contentLength = webkit_uri_response_get_content_length(response); + + // Try to get suggested filename from response headers + const gchar* suggestedName = webkit_uri_response_get_suggested_filename(response); + if (suggestedName != nullptr) { + suggestedFilename = std::string(suggestedName); + } + } + + // If no suggested filename from response, get it from download + if (!suggestedFilename.has_value()) { + // WebKitGTK automatically determines a filename + const gchar* dest = webkit_download_get_destination(download); + if (dest != nullptr) { + // Extract filename from path + std::string destStr(dest); + size_t lastSlash = destStr.rfind('/'); + if (lastSlash != std::string::npos) { + suggestedFilename = destStr.substr(lastSlash + 1); + } else { + suggestedFilename = destStr; + } + } + } +} + +FlValue* DownloadStartRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"suggestedFilename", make_fl_value(suggestedFilename)}, + {"mimeType", make_fl_value(mimeType)}, + {"contentLength", make_fl_value(contentLength)}, + {"contentDisposition", make_fl_value(contentDisposition)}, + {"userAgent", make_fl_value(userAgent)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.h b/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.h new file mode 100644 index 00000000..e307e151 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/download_start_request.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ + +#include +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +class DownloadStartRequest { + public: + std::optional url; + std::optional suggestedFilename; + std::optional mimeType; + int64_t contentLength; + std::optional contentDisposition; + std::optional userAgent; + + DownloadStartRequest(); + DownloadStartRequest(WebKitDownload* download); + ~DownloadStartRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.cc new file mode 100644 index 00000000..3df130c5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.cc @@ -0,0 +1,23 @@ +#include "download_start_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +// === DownloadStartResponse === + +DownloadStartResponse::DownloadStartResponse() : action(DownloadStartResponseAction::CANCEL) {} + +DownloadStartResponse::DownloadStartResponse(FlValue* map) + : action(DownloadStartResponseAction::CANCEL) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); + + destinationPath = get_optional_fl_map_value(map, "destinationPath"); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.h b/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.h new file mode 100644 index 00000000..61c37f19 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/download_start_response.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +enum class DownloadStartResponseAction { CANCEL = 0, ALLOW = 1 }; + +class DownloadStartResponse { + public: + DownloadStartResponseAction action; + std::optional destinationPath; + + DownloadStartResponse(); + DownloadStartResponse(FlValue* map); + ~DownloadStartResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/find_session.cc b/.vendor/flutter_inappwebview_linux/linux/types/find_session.cc new file mode 100644 index 00000000..0ca30445 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/find_session.cc @@ -0,0 +1,21 @@ +#include "find_session.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +FindSession::FindSession(int resultCount, int highlightedResultIndex) + : resultCount(resultCount), highlightedResultIndex(highlightedResultIndex) {} + +FindSession::FindSession(FlValue* value) + : resultCount(get_fl_map_value(value, "resultCount", 0)), + highlightedResultIndex(get_fl_map_value(value, "highlightedResultIndex", 0)) {} + +FlValue* FindSession::toFlValue() const { + return to_fl_map({ + {"resultCount", make_fl_value(resultCount)}, + {"highlightedResultIndex", make_fl_value(highlightedResultIndex)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/find_session.h b/.vendor/flutter_inappwebview_linux/linux/types/find_session.h new file mode 100644 index 00000000..41f97078 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/find_session.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_SESSION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_SESSION_H_ + +#include +#include + +namespace flutter_inappwebview_plugin { + +class FindSession { + public: + int resultCount; + int highlightedResultIndex; + + FindSession(int resultCount, int highlightedResultIndex); + explicit FindSession(FlValue* value); + ~FindSession() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_FIND_SESSION_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.cc b/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.cc new file mode 100644 index 00000000..e1893611 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.cc @@ -0,0 +1,55 @@ +#include "hit_test_result.h" + +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +HitTestResult HitTestResult::fromWebKitHitTestResult(void* hit_test_result_ptr) { + if (hit_test_result_ptr == nullptr) { + return HitTestResult(HitTestResultType::UNKNOWN_TYPE); + } + + auto* hit_test_result = static_cast(hit_test_result_ptr); + + // Determine the type based on WebKit hit test result context + // Priority matters: link+image = SRC_IMAGE_ANCHOR_TYPE, otherwise check each type + if (webkit_hit_test_result_context_is_link(hit_test_result) && + webkit_hit_test_result_context_is_image(hit_test_result)) { + // Image that is also a link + const char* uri = webkit_hit_test_result_get_link_uri(hit_test_result); + return HitTestResult(HitTestResultType::SRC_IMAGE_ANCHOR_TYPE, + uri ? std::optional(uri) : std::nullopt); + } else if (webkit_hit_test_result_context_is_link(hit_test_result)) { + const char* uri = webkit_hit_test_result_get_link_uri(hit_test_result); + return HitTestResult(HitTestResultType::SRC_ANCHOR_TYPE, + uri ? std::optional(uri) : std::nullopt); + } else if (webkit_hit_test_result_context_is_image(hit_test_result)) { + const char* uri = webkit_hit_test_result_get_image_uri(hit_test_result); + return HitTestResult(HitTestResultType::IMAGE_TYPE, + uri ? std::optional(uri) : std::nullopt); + } else if (webkit_hit_test_result_context_is_media(hit_test_result)) { + // Media elements (video/audio) - use IMAGE_TYPE as there's no specific media type + const char* uri = webkit_hit_test_result_get_media_uri(hit_test_result); + return HitTestResult(HitTestResultType::IMAGE_TYPE, + uri ? std::optional(uri) : std::nullopt); + } else if (webkit_hit_test_result_context_is_editable(hit_test_result)) { + return HitTestResult(HitTestResultType::EDIT_TEXT_TYPE); + } + + // Default: unknown type + return HitTestResult(HitTestResultType::UNKNOWN_TYPE); +} + +FlValue* HitTestResult::toFlValue() const { + // Convert type enum to the integer value expected by Dart + int64_t type_value = static_cast(type_); + + return to_fl_map({ + {"type", make_fl_value(type_value)}, + {"extra", make_fl_value(extra_)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.h b/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.h new file mode 100644 index 00000000..2b7d6ecf --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/hit_test_result.h @@ -0,0 +1,54 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HIT_TEST_RESULT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HIT_TEST_RESULT_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/// Hit test result types matching InAppWebViewHitTestResultType in Dart +enum class HitTestResultType { + UNKNOWN_TYPE = 0, + PHONE_TYPE = 2, + GEO_TYPE = 3, + EMAIL_TYPE = 4, + IMAGE_TYPE = 5, + SRC_ANCHOR_TYPE = 7, + SRC_IMAGE_ANCHOR_TYPE = 8, + EDIT_TEXT_TYPE = 9 +}; + +/// HitTestResult - represents the result of a hit test on the WebView +/// +/// This maps to InAppWebViewHitTestResult in Dart. +/// It contains the type of element hit and optional extra information (like a URL). +class HitTestResult { + public: + /// Default constructor - creates an unknown type hit test result + HitTestResult() : type_(HitTestResultType::UNKNOWN_TYPE) {} + + /// Constructor with type and optional extra data + HitTestResult(HitTestResultType type, const std::optional& extra = std::nullopt) + : type_(type), extra_(extra) {} + + /// Construct from a WebKitHitTestResult pointer + /// This extracts the relevant information from the WPE WebKit hit test result + static HitTestResult fromWebKitHitTestResult(void* hit_test_result); + + /// Convert to FlValue map for Dart serialization + FlValue* toFlValue() const; + + // Getters + HitTestResultType type() const { return type_; } + const std::optional& extra() const { return extra_; } + + private: + HitTestResultType type_; + std::optional extra_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HIT_TEST_RESULT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.cc new file mode 100644 index 00000000..a821fae9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.cc @@ -0,0 +1,25 @@ +#include "http_auth_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +HttpAuthResponse::HttpAuthResponse() + : action(HttpAuthResponseAction::CANCEL), permanentPersistence(false) {} + +HttpAuthResponse::HttpAuthResponse(FlValue* map) + : action(HttpAuthResponseAction::CANCEL), permanentPersistence(false) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + username = get_optional_fl_map_value(map, "username"); + password = get_optional_fl_map_value(map, "password"); + + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); + + permanentPersistence = get_fl_map_value(map, "permanentPersistence", false); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.h b/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.h new file mode 100644 index 00000000..b1eb6d54 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/http_auth_response.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * HTTP authentication response action. + */ +enum class HttpAuthResponseAction { CANCEL = 0, PROCEED = 1, USE_SAVED_CREDENTIAL = 2 }; + +/** + * Response to an HTTP authentication challenge. + */ +class HttpAuthResponse { + public: + std::optional username; + std::optional password; + HttpAuthResponseAction action; + bool permanentPersistence; + + HttpAuthResponse(); + HttpAuthResponse(FlValue* map); + ~HttpAuthResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.cc b/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.cc new file mode 100644 index 00000000..85793c0c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.cc @@ -0,0 +1,21 @@ +#include "http_authentication_challenge.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +HttpAuthenticationChallenge::HttpAuthenticationChallenge(const URLProtectionSpace& protectionSpace, + bool isRetry) + : protectionSpace(protectionSpace), isRetry(isRetry) {} + +FlValue* HttpAuthenticationChallenge::toFlValue() const { + return to_fl_map({ + {"protectionSpace", protectionSpace.toFlValue()}, + {"previousFailureCount", make_fl_value(previousFailureCount)}, + {"proposedCredential", make_fl_value()}, + {"failureResponse", make_fl_value()}, + {"error", make_fl_value()}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.h b/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.h new file mode 100644 index 00000000..b0e57eb5 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/http_authentication_challenge.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ + +#include + +#include +#include + +#include "url_protection_space.h" + +namespace flutter_inappwebview_plugin { + +/** + * HTTP authentication challenge - sent when server requires authentication. + */ +class HttpAuthenticationChallenge { + public: + URLProtectionSpace protectionSpace; + std::optional previousFailureCount; // Number of retry attempts + bool isRetry; + + HttpAuthenticationChallenge(const URLProtectionSpace& protectionSpace, bool isRetry); + ~HttpAuthenticationChallenge() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.cc b/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.cc new file mode 100644 index 00000000..20b0bf0f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.cc @@ -0,0 +1,28 @@ +#include "javascript_handler_function_data.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(const std::string& origin, + const std::string& requestUrl, + bool isMainFrame, + const std::string& args) + : origin(origin), requestUrl(requestUrl), isMainFrame(isMainFrame), args(args) {} + +JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(FlValue* map) + : origin(get_fl_map_value(map, "origin", "")), + requestUrl(get_fl_map_value(map, "requestUrl", "")), + isMainFrame(get_fl_map_value(map, "isMainFrame", false)), + args(get_fl_map_value(map, "args", "")) {} + +FlValue* JavaScriptHandlerFunctionData::toFlValue() const { + return to_fl_map({ + {"origin", make_fl_value(origin)}, + {"requestUrl", make_fl_value(requestUrl)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + {"args", make_fl_value(args)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.h b/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.h new file mode 100644 index 00000000..92cfa320 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/javascript_handler_function_data.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ + +#include + +#include + +namespace flutter_inappwebview_plugin { + +class JavaScriptHandlerFunctionData { + public: + const std::string origin; + const std::string requestUrl; + const bool isMainFrame; + const std::string args; + + JavaScriptHandlerFunctionData(const std::string& origin, const std::string& requestUrl, + bool isMainFrame, const std::string& args); + JavaScriptHandlerFunctionData(FlValue* map); + ~JavaScriptHandlerFunctionData() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.cc new file mode 100644 index 00000000..2bb0d44d --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.cc @@ -0,0 +1,19 @@ +#include "js_alert_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsAlertRequest::JsAlertRequest(const std::optional& url, const std::string& message, + bool isMainFrame) + : url(url), message(message), isMainFrame(isMainFrame) {} + +FlValue* JsAlertRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"message", make_fl_value(message)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.h b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.h new file mode 100644 index 00000000..638df650 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_request.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_REQUEST_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Represents a JavaScript alert() dialog request. + */ +class JsAlertRequest { + public: + std::optional url; + std::string message; + bool isMainFrame; + + JsAlertRequest(const std::optional& url, const std::string& message, + bool isMainFrame); + ~JsAlertRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.cc new file mode 100644 index 00000000..ce04a579 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.cc @@ -0,0 +1,22 @@ +#include "js_alert_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsAlertResponse::JsAlertResponse() + : handledByClient(false), action(JsAlertResponseAction::CONFIRM) {} + +JsAlertResponse::JsAlertResponse(FlValue* map) + : handledByClient(false), action(JsAlertResponseAction::CONFIRM) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + handledByClient = get_fl_map_value(map, "handledByClient", handledByClient); + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); + message = get_optional_fl_map_value(map, "message"); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.h b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.h new file mode 100644 index 00000000..0a7547cd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_alert_response.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Response action for JS alert dialogs. + */ +enum class JsAlertResponseAction { CONFIRM = 0 }; + +/** + * Response to a JavaScript alert() dialog. + */ +class JsAlertResponse { + public: + bool handledByClient; + JsAlertResponseAction action; + std::optional message; + + JsAlertResponse(); + JsAlertResponse(FlValue* map); + ~JsAlertResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_ALERT_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.cc new file mode 100644 index 00000000..1f026628 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.cc @@ -0,0 +1,23 @@ +#include "js_before_unload_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsBeforeUnloadResponse::JsBeforeUnloadResponse() + : handledByClient(false), shouldAllowNavigation(true) {} + +JsBeforeUnloadResponse::JsBeforeUnloadResponse(FlValue* map) + : handledByClient(false), shouldAllowNavigation(true) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + handledByClient = get_fl_map_value(map, "handledByClient", handledByClient); + // Action 1 = allow navigation + int64_t actionInt = get_fl_map_value(map, "action", 1); + shouldAllowNavigation = (actionInt == 1); + message = get_optional_fl_map_value(map, "message"); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.h b/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.h new file mode 100644 index 00000000..90e375ca --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_before_unload_response.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_BEFORE_UNLOAD_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_BEFORE_UNLOAD_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Response for beforeunload dialogs. + */ +class JsBeforeUnloadResponse { + public: + bool handledByClient; + bool shouldAllowNavigation; + std::optional message; + + JsBeforeUnloadResponse(); + JsBeforeUnloadResponse(FlValue* map); + ~JsBeforeUnloadResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_BEFORE_UNLOAD_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.cc new file mode 100644 index 00000000..d1565b0b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.cc @@ -0,0 +1,19 @@ +#include "js_confirm_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsConfirmRequest::JsConfirmRequest(const std::optional& url, + const std::string& message, bool isMainFrame) + : url(url), message(message), isMainFrame(isMainFrame) {} + +FlValue* JsConfirmRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"message", make_fl_value(message)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.h b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.h new file mode 100644 index 00000000..7ba77ed1 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_request.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_REQUEST_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Represents a JavaScript confirm() dialog request. + */ +class JsConfirmRequest { + public: + std::optional url; + std::string message; + bool isMainFrame; + + JsConfirmRequest(const std::optional& url, const std::string& message, + bool isMainFrame); + ~JsConfirmRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.cc new file mode 100644 index 00000000..2b435154 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.cc @@ -0,0 +1,21 @@ +#include "js_confirm_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsConfirmResponse::JsConfirmResponse() + : handledByClient(false), action(JsConfirmResponseAction::CANCEL) {} + +JsConfirmResponse::JsConfirmResponse(FlValue* map) + : handledByClient(false), action(JsConfirmResponseAction::CANCEL) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + handledByClient = get_fl_map_value(map, "handledByClient", handledByClient); + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.h b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.h new file mode 100644 index 00000000..a2cadcf8 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_confirm_response.h @@ -0,0 +1,28 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_RESPONSE_H_ + +#include + +namespace flutter_inappwebview_plugin { + +/** + * Response action for JS confirm dialogs. + */ +enum class JsConfirmResponseAction { CANCEL = 0, CONFIRM = 1 }; + +/** + * Response to a JavaScript confirm() dialog. + */ +class JsConfirmResponse { + public: + bool handledByClient; + JsConfirmResponseAction action; + + JsConfirmResponse(); + JsConfirmResponse(FlValue* map); + ~JsConfirmResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_CONFIRM_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.cc new file mode 100644 index 00000000..5d4e1c39 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.cc @@ -0,0 +1,20 @@ +#include "js_prompt_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsPromptRequest::JsPromptRequest(const std::optional& url, const std::string& message, + const std::optional& defaultValue, bool isMainFrame) + : url(url), message(message), defaultValue(defaultValue), isMainFrame(isMainFrame) {} + +FlValue* JsPromptRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"message", make_fl_value(message)}, + {"defaultValue", make_fl_value(defaultValue)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.h b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.h new file mode 100644 index 00000000..bbd533c7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_request.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_REQUEST_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Represents a JavaScript prompt() dialog request. + */ +class JsPromptRequest { + public: + std::optional url; + std::string message; + std::optional defaultValue; + bool isMainFrame; + + JsPromptRequest(const std::optional& url, const std::string& message, + const std::optional& defaultValue, bool isMainFrame); + ~JsPromptRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.cc new file mode 100644 index 00000000..7f73ed9c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.cc @@ -0,0 +1,22 @@ +#include "js_prompt_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +JsPromptResponse::JsPromptResponse() + : handledByClient(false), action(JsPromptResponseAction::CANCEL) {} + +JsPromptResponse::JsPromptResponse(FlValue* map) + : handledByClient(false), action(JsPromptResponseAction::CANCEL) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + handledByClient = get_fl_map_value(map, "handledByClient", handledByClient); + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); + value = get_optional_fl_map_value(map, "value"); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.h b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.h new file mode 100644 index 00000000..ea90d174 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/js_prompt_response.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Response action for JS prompt dialogs. + */ +enum class JsPromptResponseAction { CANCEL = 0, CONFIRM = 1 }; + +/** + * Response to a JavaScript prompt() dialog. + */ +class JsPromptResponse { + public: + bool handledByClient; + JsPromptResponseAction action; + std::optional value; + + JsPromptResponse(); + JsPromptResponse(FlValue* map); + ~JsPromptResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_JS_PROMPT_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.cc b/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.cc new file mode 100644 index 00000000..dc15a448 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.cc @@ -0,0 +1,24 @@ +#include "navigation_action.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +NavigationAction::NavigationAction(std::shared_ptr request, bool isForMainFrame, + const std::optional& isRedirect, + const std::optional& navigationType) + : request(std::move(request)), + isForMainFrame(isForMainFrame), + isRedirect(isRedirect), + navigationType(navigationType) {} + +FlValue* NavigationAction::toFlValue() const { + return to_fl_map({ + {"request", request ? request->toFlValue() : make_fl_value()}, + {"isForMainFrame", make_fl_value(isForMainFrame)}, + {"isRedirect", make_fl_value(isRedirect)}, + {"navigationType", make_fl_value(NavigationActionTypeToInteger(navigationType))}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.h b/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.h new file mode 100644 index 00000000..0354c19f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/navigation_action.h @@ -0,0 +1,37 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ + +#include + +#include +#include + +#include "url_request.h" + +namespace flutter_inappwebview_plugin { + +enum class NavigationActionType { linkActivated = 0, backForward, reload, other }; + +inline std::optional NavigationActionTypeToInteger( + const std::optional& action) { + return action.has_value() ? static_cast(action.value()) : std::optional{}; +} + +class NavigationAction { + public: + const std::shared_ptr request; + const bool isForMainFrame; + const std::optional isRedirect; + const std::optional navigationType; + + NavigationAction(std::shared_ptr request, bool isForMainFrame, + const std::optional& isRedirect, + const std::optional& navigationType); + ~NavigationAction() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/option_menu_popup.cc b/.vendor/flutter_inappwebview_linux/linux/types/option_menu_popup.cc new file mode 100644 index 00000000..2e4a0317 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/option_menu_popup.cc @@ -0,0 +1,665 @@ +#include "option_menu_popup.h" + +#include + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace flutter_inappwebview_plugin { + +OptionMenuPopup::OptionMenuPopup(GtkWindow* parent_window) : parent_window_(parent_window) { + // Create a popup window + popup_window_ = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_type_hint(GTK_WINDOW(popup_window_), GDK_WINDOW_TYPE_HINT_POPUP_MENU); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(popup_window_), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(popup_window_), TRUE); + gtk_window_set_decorated(GTK_WINDOW(popup_window_), FALSE); + gtk_window_set_resizable(GTK_WINDOW(popup_window_), FALSE); + + if (parent_window_ != nullptr) { + gtk_window_set_transient_for(GTK_WINDOW(popup_window_), parent_window_); + } + + // Enable RGBA for transparency + GdkScreen* screen = gtk_widget_get_screen(popup_window_); + GdkVisual* visual = gdk_screen_get_rgba_visual(screen); + if (visual != nullptr) { + gtk_widget_set_visual(popup_window_, visual); + } + gtk_widget_set_app_paintable(popup_window_, TRUE); + + // Create drawing area + drawing_area_ = gtk_drawing_area_new(); + gtk_widget_set_can_focus(drawing_area_, TRUE); + gtk_container_add(GTK_CONTAINER(popup_window_), drawing_area_); + + // Enable events on drawing area + gtk_widget_add_events(drawing_area_, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK); + + // Connect signals for drawing area + g_signal_connect(drawing_area_, "draw", G_CALLBACK(OnDraw), this); + g_signal_connect(drawing_area_, "button-press-event", G_CALLBACK(OnButtonPress), this); + g_signal_connect(drawing_area_, "button-release-event", G_CALLBACK(OnButtonRelease), this); + g_signal_connect(drawing_area_, "motion-notify-event", G_CALLBACK(OnMotionNotify), this); + g_signal_connect(drawing_area_, "leave-notify-event", G_CALLBACK(OnLeaveNotify), this); + g_signal_connect(drawing_area_, "scroll-event", G_CALLBACK(OnScroll), this); + g_signal_connect(drawing_area_, "key-press-event", G_CALLBACK(OnKeyPress), this); + + // Also connect button-press to popup window for when clicks land on window edge + g_signal_connect(popup_window_, "button-press-event", G_CALLBACK(OnButtonPress), this); + + // Connect signals for popup window + g_signal_connect(popup_window_, "focus-out-event", G_CALLBACK(OnFocusOut), this); + g_signal_connect(popup_window_, "unrealize", G_CALLBACK(OnUnrealize), this); + + gtk_widget_show(drawing_area_); +} + +OptionMenuPopup::~OptionMenuPopup() { + Hide(); // Ensure cleanup + if (popup_window_ != nullptr) { + gtk_widget_destroy(popup_window_); + popup_window_ = nullptr; + } +} + +void OptionMenuPopup::SetOptionMenu(WebKitOptionMenu* menu) { + webkit_menu_ = menu; + UpdateItems(); +} + +void OptionMenuPopup::UpdateItems() { + items_.clear(); + initially_selected_index_ = -1; + + if (webkit_menu_ == nullptr) { + return; + } + + guint n_items = webkit_option_menu_get_n_items(webkit_menu_); + + for (guint i = 0; i < n_items; i++) { + WebKitOptionMenuItem* item = webkit_option_menu_get_item(webkit_menu_, i); + + MenuItem menu_item; + const gchar* label = webkit_option_menu_item_get_label(item); + menu_item.label = label ? label : ""; + menu_item.enabled = webkit_option_menu_item_is_enabled(item); + menu_item.selected = webkit_option_menu_item_is_selected(item); + menu_item.is_group_label = webkit_option_menu_item_is_group_label(item); + menu_item.is_group_child = webkit_option_menu_item_is_group_child(item); + + if (menu_item.selected && menu_item.enabled && !menu_item.is_group_label) { + initially_selected_index_ = static_cast(i); + } + + items_.push_back(std::move(menu_item)); + } +} + +void OptionMenuPopup::UpdateSize() { + if (items_.empty()) { + width_ = MENU_MIN_WIDTH; + height_ = MENU_VERTICAL_PADDING * 2; + content_height_ = 0; + return; + } + + // Calculate width based on text + cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + cairo_t* cr = cairo_create(surface); + + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, MENU_ITEM_TEXT_SIZE); + + int max_text_width = 0; + for (const auto& item : items_) { + cairo_text_extents_t extents; + cairo_text_extents(cr, item.label.c_str(), &extents); + max_text_width = std::max(max_text_width, static_cast(extents.width)); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + // Add padding for margins + width_ = std::max(MENU_MIN_WIDTH, std::min(MENU_MAX_WIDTH, + max_text_width + MENU_HORIZONTAL_PADDING * 4)); + + // Calculate content height + content_height_ = 0; + for (const auto& item : items_) { + if (item.is_group_label) { + content_height_ += MENU_GROUP_LABEL_HEIGHT; + } else { + content_height_ += MENU_ITEM_HEIGHT; + } + } + + // Limit visible height + int max_visible_height = MENU_MAX_VISIBLE_ITEMS * MENU_ITEM_HEIGHT + MENU_VERTICAL_PADDING * 2; + height_ = std::min(content_height_ + MENU_VERTICAL_PADDING * 2, max_visible_height); +} + +void OptionMenuPopup::Show(int x, int y, int min_width) { + if (items_.empty()) { + return; + } + + UpdateSize(); + + // Use the HTML option menus using Cairo drawing. +/// This avoids focus issues with GtkMenu by using a GTK_WINDOW_POPUP +/// with custom rendering, similar to Cog browser's approach. +class OptionMenuPopup { + public: + explicit OptionMenuPopup(GtkWindow* parent_window); + ~OptionMenuPopup(); + + /// Set the WebKit option menu to display + void SetOptionMenu(WebKitOptionMenu* menu); + + /// Show the popup at the specified position (screen coordinates) + /// min_width is the minimum width based on the HTML element's width + void Show(int x, int y, int min_width = 0); + + /// Hide the popup and clean up + void Hide(); + + /// Check if the popup is currently visible + bool IsVisible() const { return visible_; } + + /// Set callback when an item is selected + void SetItemSelectedCallback(std::function callback) { + item_selected_callback_ = std::move(callback); + } + + /// Set callback when the menu is dismissed without selection + void SetDismissedCallback(std::function callback) { + dismissed_callback_ = std::move(callback); + } + + private: + struct MenuItem { + std::string label; + bool enabled = true; + bool selected = false; + bool is_group_label = false; + bool is_group_child = false; + }; + + // Layout constants (matching Cog browser style) + static constexpr int MENU_VERTICAL_PADDING = 8; + static constexpr int MENU_HORIZONTAL_PADDING = 12; + static constexpr int MENU_ITEM_HEIGHT = 32; + static constexpr int MENU_GROUP_LABEL_HEIGHT = 28; + static constexpr int MENU_ITEM_TEXT_SIZE = 14; + static constexpr int MENU_MIN_WIDTH = 150; + static constexpr int MENU_MAX_WIDTH = 400; + static constexpr int MENU_MAX_VISIBLE_ITEMS = 10; + + void UpdateItems(); + void UpdateSize(); + void Paint(); + int GetItemAtPosition(int x, int y) const; + void ScrollToItem(int index); + + // GTK signal handlers + static gboolean OnDraw(GtkWidget* widget, cairo_t* cr, gpointer user_data); + static gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static gboolean OnButtonRelease(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, gpointer user_data); + static gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, gpointer user_data); + static gboolean OnScroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data); + static gboolean OnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data); + static gboolean OnFocusOut(GtkWidget* widget, GdkEventFocus* event, gpointer user_data); + static gboolean OnParentButtonPress(GtkWidget* widget, GdkEventButton* event, gpointer user_data); + static void OnUnrealize(GtkWidget* widget, gpointer user_data); + + GtkWindow* parent_window_ = nullptr; + GtkWidget* popup_window_ = nullptr; + GtkWidget* drawing_area_ = nullptr; + + WebKitOptionMenu* webkit_menu_ = nullptr; + std::vector items_; + + int width_ = 0; + int height_ = 0; + int content_height_ = 0; // Total height of all items + int scroll_offset_ = 0; // Current scroll position + int hovered_index_ = -1; + int pressed_index_ = -1; + int initially_selected_index_ = -1; + + int menu_x_ = 0; + int menu_y_ = 0; + + bool visible_ = false; + + gulong parent_button_handler_id_ = 0; + + std::function item_selected_callback_; + std::function dismissed_callback_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_OPTION_MENU_POPUP_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/permission_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/permission_request.cc new file mode 100644 index 00000000..ad9a534f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/permission_request.cc @@ -0,0 +1,56 @@ +#include "permission_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +PermissionRequest::PermissionRequest(const std::optional& origin, + const std::vector& resourceTypes) + : origin(origin), webkitRequest(nullptr) { + for (const auto& type : resourceTypes) { + resources.push_back(static_cast(type)); + } +} + +FlValue* PermissionRequest::toFlValue() const { + return to_fl_map({ + {"origin", make_fl_value(origin)}, + {"resources", make_fl_value(resources)}, + {"frame", make_fl_value()}, // always null for WPE WebKit + }); +} + +std::vector PermissionRequest::getResourceTypes( + WebKitPermissionRequest* request) { + std::vector types; + + if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) { + types.push_back(PermissionResourceType::GEOLOCATION); + } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) { + types.push_back(PermissionResourceType::NOTIFICATIONS); + } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { + gboolean isAudio = FALSE; + gboolean isVideo = FALSE; + + g_object_get(request, "is-for-audio-device", &isAudio, "is-for-video-device", &isVideo, + nullptr); + + if (isAudio && isVideo) { + types.push_back(PermissionResourceType::CAMERA_AND_MICROPHONE); + } else if (isAudio) { + types.push_back(PermissionResourceType::MICROPHONE); + } else if (isVideo) { + types.push_back(PermissionResourceType::CAMERA); + } + } + // Note: WEBKIT_IS_POINTER_LOCK_PERMISSION_REQUEST is not available in WPE WebKit + else if (WEBKIT_IS_DEVICE_INFO_PERMISSION_REQUEST(request)) { + types.push_back(PermissionResourceType::DEVICE_INFO); + } else if (WEBKIT_IS_MEDIA_KEY_SYSTEM_PERMISSION_REQUEST(request)) { + types.push_back(PermissionResourceType::PROTECTED_MEDIA_ID); + } + + return types; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/permission_request.h b/.vendor/flutter_inappwebview_linux/linux/types/permission_request.h new file mode 100644 index 00000000..7ac101c6 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/permission_request.h @@ -0,0 +1,54 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_REQUEST_H_ + +#include +#include + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Permission resource types that can be requested. + */ +enum class PermissionResourceType { + CAMERA = 0, + MICROPHONE = 1, + CAMERA_AND_MICROPHONE = 2, + GEOLOCATION = 3, + NOTIFICATIONS = 4, + PROTECTED_MEDIA_ID = 5, // EME/DRM permission (WebKitMediaKeySystemPermissionRequest) + MIDI_SYSEX = 6, // Not directly supported + DEVICE_INFO = 7, + POINTER_LOCK = 8 +}; + +/** + * Represents a permission request from web content. + */ +class PermissionRequest { + public: + std::optional origin; + std::vector resources; + + // Reference to the WebKit request to allow/deny later + WebKitPermissionRequest* webkitRequest; + + PermissionRequest(const std::optional& origin, + const std::vector& resourceTypes); + ~PermissionRequest() = default; + + FlValue* toFlValue() const; + + /** + * Determine the permission resource type from a WebKitPermissionRequest. + */ + static std::vector getResourceTypes(WebKitPermissionRequest* request); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/permission_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/permission_response.cc new file mode 100644 index 00000000..23972933 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/permission_response.cc @@ -0,0 +1,29 @@ +#include "permission_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +PermissionResponse::PermissionResponse() : action(PermissionResponseAction::DENY) {} + +PermissionResponse::PermissionResponse(FlValue* map) : action(PermissionResponseAction::DENY) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + int64_t actionInt = get_fl_map_value(map, "action", 0); + action = static_cast(actionInt); + + FlValue* resourcesValue = fl_value_lookup_string(map, "resources"); + if (resourcesValue != nullptr && fl_value_get_type(resourcesValue) == FL_VALUE_TYPE_LIST) { + size_t len = fl_value_get_length(resourcesValue); + for (size_t i = 0; i < len; i++) { + FlValue* item = fl_value_get_list_value(resourcesValue, i); + if (fl_value_get_type(item) == FL_VALUE_TYPE_INT) { + resources.push_back(fl_value_get_int(item)); + } + } + } +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/permission_response.h b/.vendor/flutter_inappwebview_linux/linux/types/permission_response.h new file mode 100644 index 00000000..4eeba7e8 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/permission_response.h @@ -0,0 +1,35 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * Permission response actions. + */ +enum class PermissionResponseAction { + DENY = 0, + GRANT = 1, + PROMPT = 2 // Not used on Linux - will default to deny +}; + +/** + * Response to a permission request. + */ +class PermissionResponse { + public: + std::vector resources; + PermissionResponseAction action; + + PermissionResponse(); + PermissionResponse(FlValue* map); + ~PermissionResponse() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.cc b/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.cc new file mode 100644 index 00000000..ae93ca09 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.cc @@ -0,0 +1,22 @@ +#include "plugin_script.h" + +namespace flutter_inappwebview_plugin { + +PluginScript::PluginScript(const std::string& groupName, const std::string& source, + UserScriptInjectionTime injectionTime, bool forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld, + bool requiredInAllContentWorlds, + const std::vector& messageHandlerNames) + : UserScript(groupName, source, injectionTime, forMainFrameOnly, allowedOriginRules, + contentWorld), + requiredInAllContentWorlds(requiredInAllContentWorlds), + messageHandlerNames(messageHandlerNames) {} + +bool PluginScript::operator==(const PluginScript& other) const { + return UserScript::operator==(other) && + requiredInAllContentWorlds == other.requiredInAllContentWorlds && + messageHandlerNames == other.messageHandlerNames; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.h b/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.h new file mode 100644 index 00000000..2de163c6 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/plugin_script.h @@ -0,0 +1,35 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ + +#include +#include +#include + +#include "user_script.h" + +namespace flutter_inappwebview_plugin { + +/** + * Represents an internal plugin script that is required for WebView functionality. + * Plugin scripts extend user scripts with additional properties like: + * - requiredInAllContentWorlds: whether the script needs to run in all content worlds + * - messageHandlerNames: names of message handlers this script uses + */ +class PluginScript : public UserScript { + public: + bool requiredInAllContentWorlds; + std::vector messageHandlerNames; + + PluginScript(const std::string& groupName, const std::string& source, + UserScriptInjectionTime injectionTime, bool forMainFrameOnly, + const std::optional>& allowedOriginRules = std::nullopt, + std::shared_ptr contentWorld = nullptr, + bool requiredInAllContentWorlds = false, + const std::vector& messageHandlerNames = {}); + + bool operator==(const PluginScript& other) const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.cc b/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.cc new file mode 100644 index 00000000..19a1de2c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.cc @@ -0,0 +1,32 @@ +#include "render_process_gone_detail.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +RenderProcessGoneDetail::RenderProcessGoneDetail(WebKitWebProcessTerminationReason reason) { + // Map WPE WebKit termination reasons to didCrash: + // - WEBKIT_WEB_PROCESS_CRASHED: The web process crashed -> didCrash = true + // - WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT: Killed by system due to memory -> didCrash = false + // - WEBKIT_WEB_PROCESS_TERMINATED_BY_API: Terminated via API call -> didCrash = false + switch (reason) { + case WEBKIT_WEB_PROCESS_CRASHED: + did_crash_ = true; + break; + case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT: + case WEBKIT_WEB_PROCESS_TERMINATED_BY_API: + default: + did_crash_ = false; + break; + } +} + +FlValue* RenderProcessGoneDetail::toFlValue() const { + return to_fl_map({ + {"didCrash", make_fl_value(did_crash_)}, + // rendererPriorityAtExit - WPE WebKit doesn't provide this, so it's always null + {"rendererPriorityAtExit", make_fl_value(nullptr)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.h b/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.h new file mode 100644 index 00000000..b4fa6389 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/render_process_gone_detail.h @@ -0,0 +1,42 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ + +#include +#include + +#include + +namespace flutter_inappwebview_plugin { + +/** + * RenderProcessGoneDetail - Provides details about why the web process terminated. + * + * Maps to Dart's RenderProcessGoneDetail class in platform_interface. + * Used by the onRenderProcessGone event. + */ +class RenderProcessGoneDetail { + public: + /** + * Construct from WPE WebKit termination reason. + */ + explicit RenderProcessGoneDetail(WebKitWebProcessTerminationReason reason); + + /** + * Whether the render process crashed (as opposed to being killed by the system). + */ + bool didCrash() const { return did_crash_; } + + /** + * Convert to FlValue for sending to Dart. + */ + FlValue* toFlValue() const; + + private: + bool did_crash_ = false; + // Note: WPE WebKit doesn't provide renderer priority information like Android does, + // so rendererPriorityAtExit is always null for Linux. +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.cc new file mode 100644 index 00000000..82c1957b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.cc @@ -0,0 +1,40 @@ +#include "server_trust_auth_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +ServerTrustAuthResponse::ServerTrustAuthResponse(ServerTrustAuthResponseAction action) + : action(action) {} + +std::optional ServerTrustAuthResponse::fromFlValue(FlValue* value) { + if (value == nullptr || fl_value_get_type(value) == FL_VALUE_TYPE_NULL) { + return std::nullopt; + } + + if (fl_value_get_type(value) != FL_VALUE_TYPE_MAP) { + return std::nullopt; + } + + // Get the action field + FlValue* actionValue = fl_value_lookup_string(value, "action"); + if (actionValue == nullptr || fl_value_get_type(actionValue) == FL_VALUE_TYPE_NULL) { + return ServerTrustAuthResponse(ServerTrustAuthResponseAction::CANCEL); + } + + int64_t actionInt = fl_value_get_int(actionValue); + ServerTrustAuthResponseAction action; + switch (actionInt) { + case 1: + action = ServerTrustAuthResponseAction::PROCEED; + break; + case 0: + default: + action = ServerTrustAuthResponseAction::CANCEL; + break; + } + + return ServerTrustAuthResponse(action); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.h b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.h new file mode 100644 index 00000000..b4e2a06b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_auth_response.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ + +#include + +#include + +namespace flutter_inappwebview_plugin { + +/** + * Action to take in response to a server trust authentication challenge. + * Maps to Dart's ServerTrustAuthResponseAction. + */ +enum class ServerTrustAuthResponseAction { + CANCEL = 0, // Reject the certificate and cancel the request + PROCEED = 1, // Accept the certificate and continue the request +}; + +/** + * Response to a server trust authentication challenge. + * Maps to Dart's ServerTrustAuthResponse. + */ +class ServerTrustAuthResponse { + public: + ServerTrustAuthResponseAction action; + + explicit ServerTrustAuthResponse(ServerTrustAuthResponseAction action = ServerTrustAuthResponseAction::CANCEL); + ~ServerTrustAuthResponse() = default; + + /** + * Create from FlValue map (from Dart response). + */ + static std::optional fromFlValue(FlValue* value); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.cc b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.cc new file mode 100644 index 00000000..6e60d191 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.cc @@ -0,0 +1,174 @@ +#include "server_trust_challenge.h" + +#include +#include + +#include "../utils/flutter.h" +#include "../utils/uri.h" + +namespace flutter_inappwebview_plugin { + +// === SslError === + +SslError::SslError(SslErrorType code, const std::optional& message) + : code(code), message(message) {} + +FlValue* SslError::toFlValue() const { + std::string codeStr; + switch (code) { + case SslErrorType::NOT_YET_VALID: + codeStr = "NOT_YET_VALID"; + break; + case SslErrorType::EXPIRED: + codeStr = "EXPIRED"; + break; + case SslErrorType::IDMISMATCH: + codeStr = "IDMISMATCH"; + break; + case SslErrorType::UNTRUSTED: + codeStr = "UNTRUSTED"; + break; + case SslErrorType::REVOKED: + codeStr = "REVOKED"; + break; + case SslErrorType::INSECURE: + codeStr = "INSECURE"; + break; + case SslErrorType::INVALID: + default: + codeStr = "INVALID"; + break; + } + + return to_fl_map({ + {"code", make_fl_value(codeStr)}, + {"message", make_fl_value(message)}, + }); +} + +SslError SslError::fromGTlsCertificateFlags(GTlsCertificateFlags flags) { + // Map the most significant error + // WPE WebKit may set multiple flags, we pick the most relevant one + SslErrorType code; + std::string message; + + if (flags & G_TLS_CERTIFICATE_UNKNOWN_CA) { + code = SslErrorType::UNTRUSTED; + message = "The signing certificate authority is not known."; + } else if (flags & G_TLS_CERTIFICATE_BAD_IDENTITY) { + code = SslErrorType::IDMISMATCH; + message = "The certificate does not match the expected identity of the site."; + } else if (flags & G_TLS_CERTIFICATE_EXPIRED) { + code = SslErrorType::EXPIRED; + message = "The certificate has expired."; + } else if (flags & G_TLS_CERTIFICATE_NOT_ACTIVATED) { + code = SslErrorType::NOT_YET_VALID; + message = "The certificate's activation time is still in the future."; + } else if (flags & G_TLS_CERTIFICATE_REVOKED) { + code = SslErrorType::REVOKED; + message = "The certificate has been revoked."; + } else if (flags & G_TLS_CERTIFICATE_INSECURE) { + code = SslErrorType::INSECURE; + message = "The certificate's algorithm is considered insecure."; + } else if (flags & G_TLS_CERTIFICATE_GENERIC_ERROR) { + code = SslErrorType::INVALID; + message = "A generic error occurred validating the certificate."; + } else { + code = SslErrorType::INVALID; + message = "Unknown TLS certificate error."; + } + + return SslError(code, message); +} + +// === ServerTrustURLProtectionSpace === + +ServerTrustURLProtectionSpace::ServerTrustURLProtectionSpace( + const std::string& host, int64_t port, + const std::optional& protocol, + std::shared_ptr sslCertificate, + std::shared_ptr sslError) + : host(host), port(port), protocol(protocol), sslCertificate(sslCertificate), + sslError(sslError) {} + +FlValue* ServerTrustURLProtectionSpace::toFlValue() const { + return to_fl_map({ + {"host", make_fl_value(host)}, + {"port", make_fl_value(port)}, + {"protocol", make_fl_value(protocol)}, + {"sslCertificate", sslCertificate ? sslCertificate->toFlValue() : fl_value_new_null()}, + {"sslError", sslError ? sslError->toFlValue() : fl_value_new_null()}, + // Include authenticationMethod as SERVER_TRUST + {"authenticationMethod", make_fl_value(std::string("NSURLAuthenticationMethodServerTrust"))}, + }); +} + +// === ServerTrustChallenge === + +ServerTrustChallenge::ServerTrustChallenge( + std::shared_ptr protectionSpace) + : protectionSpace(protectionSpace) {} + +FlValue* ServerTrustChallenge::toFlValue() const { + return to_fl_map({ + {"protectionSpace", protectionSpace ? protectionSpace->toFlValue() : fl_value_new_null()}, + }); +} + +std::unique_ptr ServerTrustChallenge::fromTlsError( + const std::string& failingUri, GTlsCertificate* certificate, GTlsCertificateFlags errors) { + + // Parse host and port from the failing URI + std::string host = get_host_from_url(failingUri); + int64_t port = 443; // Default to HTTPS port + + // Try to extract port from URL + // Simple parsing - look for :port after host + size_t schemeEnd = failingUri.find("://"); + if (schemeEnd != std::string::npos) { + size_t hostStart = schemeEnd + 3; + size_t pathStart = failingUri.find('/', hostStart); + std::string hostPort = (pathStart != std::string::npos) ? + failingUri.substr(hostStart, pathStart - hostStart) : + failingUri.substr(hostStart); + + size_t colonPos = hostPort.rfind(':'); + if (colonPos != std::string::npos) { + try { + port = std::stoll(hostPort.substr(colonPos + 1)); + } catch (...) { + // Keep default port + } + } + } + + // Extract protocol + std::optional protocol; + std::string scheme = get_scheme_from_url(failingUri); + if (!scheme.empty()) { + protocol = scheme; + } + + // Extract certificate data + std::shared_ptr sslCertificate; + if (certificate != nullptr) { + GByteArray* certData = nullptr; + g_object_get(certificate, "certificate", &certData, nullptr); + if (certData != nullptr) { + std::vector derData(certData->data, certData->data + certData->len); + sslCertificate = std::make_shared(derData); + g_byte_array_unref(certData); + } + } + + // Convert error flags to SslError + auto sslError = std::make_shared(SslError::fromGTlsCertificateFlags(errors)); + + // Create protection space + auto protectionSpace = std::make_shared( + host, port, protocol, sslCertificate, sslError); + + return std::make_unique(protectionSpace); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.h b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.h new file mode 100644 index 00000000..17d361a0 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/server_trust_challenge.h @@ -0,0 +1,90 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "ssl_certificate.h" + +namespace flutter_inappwebview_plugin { + +/** + * SSL Error type - maps to Dart's SslErrorType. + * Based on GTlsCertificateFlags. + */ +enum class SslErrorType { + INVALID = 0, // G_TLS_CERTIFICATE_GENERIC_ERROR + NOT_YET_VALID = 1, // G_TLS_CERTIFICATE_NOT_ACTIVATED + EXPIRED = 2, // G_TLS_CERTIFICATE_EXPIRED + IDMISMATCH = 3, // G_TLS_CERTIFICATE_BAD_IDENTITY + UNTRUSTED = 4, // G_TLS_CERTIFICATE_UNKNOWN_CA + REVOKED = 5, // G_TLS_CERTIFICATE_REVOKED + INSECURE = 6, // G_TLS_CERTIFICATE_INSECURE +}; + +/** + * SSL Error information - maps to Dart's SslError class. + */ +class SslError { + public: + SslErrorType code; + std::optional message; + + SslError(SslErrorType code, const std::optional& message); + ~SslError() = default; + + FlValue* toFlValue() const; + + static SslError fromGTlsCertificateFlags(GTlsCertificateFlags flags); +}; + +/** + * URL Protection Space for server trust authentication. + * Extended version that includes SSL certificate and error info. + */ +class ServerTrustURLProtectionSpace { + public: + std::string host; + int64_t port; + std::optional protocol; + std::shared_ptr sslCertificate; + std::shared_ptr sslError; + + ServerTrustURLProtectionSpace(const std::string& host, int64_t port, + const std::optional& protocol, + std::shared_ptr sslCertificate, + std::shared_ptr sslError); + ~ServerTrustURLProtectionSpace() = default; + + FlValue* toFlValue() const; +}; + +/** + * Server Trust Challenge - sent when a TLS error occurs. + * Maps to Dart's ServerTrustChallenge class. + */ +class ServerTrustChallenge { + public: + std::shared_ptr protectionSpace; + + explicit ServerTrustChallenge(std::shared_ptr protectionSpace); + ~ServerTrustChallenge() = default; + + FlValue* toFlValue() const; + + /** + * Create a ServerTrustChallenge from WPE WebKit TLS error parameters. + */ + static std::unique_ptr fromTlsError( + const std::string& failingUri, GTlsCertificate* certificate, GTlsCertificateFlags errors); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.cc new file mode 100644 index 00000000..98eee6c8 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.cc @@ -0,0 +1,36 @@ +#include "show_file_chooser_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +ShowFileChooserResponse::ShowFileChooserResponse() + : handledByClient(false), filePaths(std::nullopt) {} + +ShowFileChooserResponse::ShowFileChooserResponse( + bool handledByClient, std::optional> filePaths) + : handledByClient(handledByClient), filePaths(std::move(filePaths)) {} + +ShowFileChooserResponse ShowFileChooserResponse::fromFlValue(FlValue* value) { + if (value == nullptr || fl_value_get_type(value) == FL_VALUE_TYPE_NULL) { + return ShowFileChooserResponse(); + } + + if (fl_value_get_type(value) != FL_VALUE_TYPE_MAP) { + return ShowFileChooserResponse(); + } + + auto handledByClient = get_fl_map_value(value, "handledByClient", false); + auto filePaths = get_optional_fl_map_value>(value, "filePaths"); + + return ShowFileChooserResponse(handledByClient, std::move(filePaths)); +} + +FlValue* ShowFileChooserResponse::toFlValue() const { + return to_fl_map({ + {"handledByClient", make_fl_value(handledByClient)}, + {"filePaths", make_fl_value(filePaths)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.h b/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.h new file mode 100644 index 00000000..fef590f7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/show_file_chooser_response.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SHOW_FILE_CHOOSER_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SHOW_FILE_CHOOSER_RESPONSE_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class ShowFileChooserResponse { + public: + bool handledByClient; + std::optional> filePaths; + + ShowFileChooserResponse(); + ShowFileChooserResponse(bool handledByClient, + std::optional> filePaths); + ~ShowFileChooserResponse() = default; + + static ShowFileChooserResponse fromFlValue(FlValue* value); + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_SHOW_FILE_CHOOSER_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.cc b/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.cc new file mode 100644 index 00000000..c19b020b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.cc @@ -0,0 +1,19 @@ +#include "ssl_certificate.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +SslCertificate::SslCertificate(const std::optional>& x509Certificate) + : x509Certificate(x509Certificate) {} + +SslCertificate::SslCertificate(FlValue* map) + : x509Certificate(get_optional_fl_map_value>(map, "x509Certificate")) {} + +FlValue* SslCertificate::toFlValue() const { + return to_fl_map({ + {"x509Certificate", make_fl_value(x509Certificate)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.h b/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.h new file mode 100644 index 00000000..ab1bdc2c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/ssl_certificate.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/// SSL Certificate information for HTTPS connections. +/// Maps to Dart's SslCertificate class. +class SslCertificate { + public: + /// The raw X.509 certificate data in DER format. + const std::optional> x509Certificate; + + /// Constructor with raw certificate data. + explicit SslCertificate(const std::optional>& x509Certificate); + + /// Constructor from FlValue map (for deserialization from Dart). + explicit SslCertificate(FlValue* map); + + ~SslCertificate() = default; + + /// Convert to FlValue map for sending to Dart. + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_credential.cc b/.vendor/flutter_inappwebview_linux/linux/types/url_credential.cc new file mode 100644 index 00000000..24302e53 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_credential.cc @@ -0,0 +1,29 @@ +#include "url_credential.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +URLCredential::URLCredential() {} + +URLCredential::URLCredential(const std::optional& username, + const std::optional& password) + : username(username), password(password) {} + +URLCredential::URLCredential(FlValue* map) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + username = get_optional_fl_map_value(map, "username"); + password = get_optional_fl_map_value(map, "password"); +} + +FlValue* URLCredential::toFlValue() const { + return to_fl_map({ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_credential.h b/.vendor/flutter_inappwebview_linux/linux/types/url_credential.h new file mode 100644 index 00000000..f6a2af65 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_credential.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * URL credential for storing/providing authentication credentials. + */ +class URLCredential { + public: + std::optional username; + std::optional password; + + URLCredential(); + URLCredential(const std::optional& username, + const std::optional& password); + URLCredential(FlValue* map); + ~URLCredential() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.cc b/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.cc new file mode 100644 index 00000000..f061b56a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.cc @@ -0,0 +1,80 @@ +#include "url_protection_space.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +// Convert HttpAuthScheme to the string values expected by the platform interface +static std::optional authSchemeToString(HttpAuthScheme scheme) { + switch (scheme) { + case HttpAuthScheme::DEFAULT: + return "NSURLAuthenticationMethodDefault"; + case HttpAuthScheme::HTTP_BASIC: + return "NSURLAuthenticationMethodHTTPBasic"; + case HttpAuthScheme::HTTP_DIGEST: + return "NSURLAuthenticationMethodHTTPDigest"; + case HttpAuthScheme::HTML_FORM: + return "NSURLAuthenticationMethodHTMLForm"; + case HttpAuthScheme::NTLM: + return "NSURLAuthenticationMethodNTLM"; + case HttpAuthScheme::NEGOTIATE: + return "NSURLAuthenticationMethodNegotiate"; + case HttpAuthScheme::CLIENT_CERTIFICATE: + return "NSURLAuthenticationMethodClientCertificate"; + case HttpAuthScheme::SERVER_TRUST: + return "NSURLAuthenticationMethodServerTrust"; + case HttpAuthScheme::UNKNOWN: + default: + return std::nullopt; + } +} + +URLProtectionSpace::URLProtectionSpace(const std::string& host, int64_t port, + const std::optional& protocol, + const std::optional& realm, + HttpAuthScheme authenticationMethod, bool isProxy) + : host(host), + port(port), + protocol(protocol), + realm(realm), + authenticationMethod(authenticationMethod), + isProxy(isProxy) {} + +FlValue* URLProtectionSpace::toFlValue() const { + auto authMethodStr = authSchemeToString(authenticationMethod); + return to_fl_map({ + {"host", make_fl_value(host)}, + {"port", make_fl_value(port)}, + {"protocol", make_fl_value(protocol)}, + {"realm", make_fl_value(realm)}, + {"authenticationMethod", make_fl_value(authMethodStr)}, + {"isProxy", make_fl_value(isProxy)}, + {"sslCertificate", make_fl_value()}, + {"sslError", make_fl_value()}, + }); +} + +HttpAuthScheme URLProtectionSpace::fromWebKitScheme(WebKitAuthenticationScheme scheme) { + switch (scheme) { + case WEBKIT_AUTHENTICATION_SCHEME_DEFAULT: + return HttpAuthScheme::DEFAULT; + case WEBKIT_AUTHENTICATION_SCHEME_HTTP_BASIC: + return HttpAuthScheme::HTTP_BASIC; + case WEBKIT_AUTHENTICATION_SCHEME_HTTP_DIGEST: + return HttpAuthScheme::HTTP_DIGEST; + case WEBKIT_AUTHENTICATION_SCHEME_HTML_FORM: + return HttpAuthScheme::HTML_FORM; + case WEBKIT_AUTHENTICATION_SCHEME_NTLM: + return HttpAuthScheme::NTLM; + case WEBKIT_AUTHENTICATION_SCHEME_NEGOTIATE: + return HttpAuthScheme::NEGOTIATE; + case WEBKIT_AUTHENTICATION_SCHEME_CLIENT_CERTIFICATE_REQUESTED: + return HttpAuthScheme::CLIENT_CERTIFICATE; + case WEBKIT_AUTHENTICATION_SCHEME_SERVER_TRUST_EVALUATION_REQUESTED: + return HttpAuthScheme::SERVER_TRUST; + default: + return HttpAuthScheme::UNKNOWN; + } +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.h b/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.h new file mode 100644 index 00000000..8a73b443 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_protection_space.h @@ -0,0 +1,53 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ + +#include +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +/** + * HTTP authentication scheme types. + */ +enum class HttpAuthScheme { + DEFAULT = 0, + HTTP_BASIC = 1, + HTTP_DIGEST = 2, + HTML_FORM = 3, + NTLM = 4, + NEGOTIATE = 5, + CLIENT_CERTIFICATE = 6, + SERVER_TRUST = 7, + UNKNOWN = -1 +}; + +/** + * URL protection space - describes the realm requiring authentication. + */ +class URLProtectionSpace { + public: + std::string host; + int64_t port; + std::optional protocol; + std::optional realm; + HttpAuthScheme authenticationMethod; + bool isProxy; + + URLProtectionSpace(const std::string& host, int64_t port, + const std::optional& protocol, + const std::optional& realm, HttpAuthScheme authenticationMethod, + bool isProxy); + ~URLProtectionSpace() = default; + + FlValue* toFlValue() const; + + static HttpAuthScheme fromWebKitScheme(WebKitAuthenticationScheme scheme); +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/url_request.cc new file mode 100644 index 00000000..710ce75a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_request.cc @@ -0,0 +1,28 @@ +#include "url_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +URLRequest::URLRequest(const std::optional& url, + const std::optional& method, + const std::optional>& headers, + const std::optional>& body) + : url(url), method(method), headers(headers), body(body) {} + +URLRequest::URLRequest(FlValue* map) + : url(get_optional_fl_map_value(map, "url")), + method(get_optional_fl_map_value(map, "method")), + headers(get_optional_fl_map_value>(map, "headers")), + body(get_optional_fl_map_value>(map, "body")) {} + +FlValue* URLRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"body", make_fl_value(body)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/url_request.h b/.vendor/flutter_inappwebview_linux/linux/types/url_request.h new file mode 100644 index 00000000..dea9fff4 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/url_request.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ + +#include + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class URLRequest { + public: + const std::optional url; + const std::optional method; + const std::optional> headers; + const std::optional> body; + + URLRequest(const std::optional& url, const std::optional& method, + const std::optional>& headers, + const std::optional>& body); + + URLRequest(FlValue* map); + + ~URLRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/user_script.cc b/.vendor/flutter_inappwebview_linux/linux/types/user_script.cc new file mode 100644 index 00000000..cdfbb4af --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/user_script.cc @@ -0,0 +1,58 @@ +#include "user_script.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +UserScript::UserScript(const std::optional& groupName, const std::string& source, + UserScriptInjectionTime injectionTime, bool forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld) + : groupName(groupName), + source(source), + injectionTime(injectionTime), + forMainFrameOnly(forMainFrameOnly), + allowedOriginRules(allowedOriginRules), + contentWorld(contentWorld ? contentWorld : ContentWorld::page()) {} + +UserScript::UserScript(FlValue* map) + : injectionTime(UserScriptInjectionTime::atDocumentStart), forMainFrameOnly(true) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return; + } + + groupName = get_optional_fl_map_value(map, "groupName"); + source = get_fl_map_value(map, "source", ""); + + int64_t injectionTimeValue = get_fl_map_value(map, "injectionTime", 0); + injectionTime = static_cast(injectionTimeValue); + + forMainFrameOnly = get_fl_map_value(map, "forMainFrameOnly", true); + + allowedOriginRules = get_optional_fl_map_value>(map, "allowedOriginRules"); + + FlValue* contentWorldValue = fl_value_lookup_string(map, "contentWorld"); + if (contentWorldValue != nullptr && fl_value_get_type(contentWorldValue) == FL_VALUE_TYPE_MAP) { + contentWorld = std::make_shared(contentWorldValue); + } else { + contentWorld = ContentWorld::page(); + } +} + +FlValue* UserScript::toFlValue() const { + return to_fl_map({ + {"groupName", make_fl_value(groupName)}, + {"source", make_fl_value(source)}, + {"injectionTime", make_fl_value(static_cast(injectionTime))}, + {"forMainFrameOnly", make_fl_value(forMainFrameOnly)}, + {"allowedOriginRules", make_fl_value(allowedOriginRules)}, + {"contentWorld", contentWorld ? contentWorld->toFlValue() : make_fl_value()}, + }); +} + +bool UserScript::operator==(const UserScript& other) const { + return groupName == other.groupName && source == other.source && + injectionTime == other.injectionTime && forMainFrameOnly == other.forMainFrameOnly; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/user_script.h b/.vendor/flutter_inappwebview_linux/linux/types/user_script.h new file mode 100644 index 00000000..0fcc622a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/user_script.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ + +#include + +#include +#include +#include +#include + +#include "content_world.h" + +namespace flutter_inappwebview_plugin { + +enum class UserScriptInjectionTime { + atDocumentStart = 0, + atDocumentEnd = 1, +}; + +/** + * Represents a user script to be injected into the web view. + */ +class UserScript { + public: + std::optional groupName; + std::string source; + UserScriptInjectionTime injectionTime; + bool forMainFrameOnly; + std::optional> allowedOriginRules; + std::shared_ptr contentWorld; + + UserScript(const std::optional& groupName, const std::string& source, + UserScriptInjectionTime injectionTime, bool forMainFrameOnly, + const std::optional>& allowedOriginRules = std::nullopt, + std::shared_ptr contentWorld = nullptr); + + UserScript(FlValue* map); + + FlValue* toFlValue() const; + + bool operator==(const UserScript& other) const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.cc b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.cc new file mode 100644 index 00000000..e577f2ef --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.cc @@ -0,0 +1,21 @@ +#include "web_resource_error.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +WebResourceError::WebResourceError(const std::string& description, int64_t type) + : description(description), type(type) {} + +WebResourceError::WebResourceError(FlValue* map) + : description(get_fl_map_value(map, "description", "")), + type(get_fl_map_value(map, "type", 399)) {} + +FlValue* WebResourceError::toFlValue() const { + return to_fl_map({ + {"description", make_fl_value(description)}, + {"type", make_fl_value(type)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.h b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.h new file mode 100644 index 00000000..48a5c91f --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_error.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ + +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +class WebResourceError { + public: + const std::string description; + const int64_t type; + + WebResourceError(const std::string& description, int64_t type); + WebResourceError(FlValue* map); + ~WebResourceError() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.cc b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.cc new file mode 100644 index 00000000..cb365332 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.cc @@ -0,0 +1,28 @@ +#include "web_resource_request.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +WebResourceRequest::WebResourceRequest( + const std::optional& url, const std::optional& method, + const std::optional>& headers, + const std::optional& isForMainFrame) + : url(url), method(method), headers(headers), isForMainFrame(isForMainFrame) {} + +WebResourceRequest::WebResourceRequest(FlValue* map) + : url(get_optional_fl_map_value(map, "url")), + method(get_optional_fl_map_value(map, "method")), + headers(get_optional_fl_map_value>(map, "headers")), + isForMainFrame(get_optional_fl_map_value(map, "isForMainFrame")) {} + +FlValue* WebResourceRequest::toFlValue() const { + return to_fl_map({ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"isForMainFrame", make_fl_value(isForMainFrame)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.h b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.h new file mode 100644 index 00000000..2616413b --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_request.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class WebResourceRequest { + public: + std::optional url; + std::optional method; + std::optional> headers; + std::optional isForMainFrame; + + WebResourceRequest(const std::optional& url, + const std::optional& method, + const std::optional>& headers, + const std::optional& isForMainFrame); + WebResourceRequest(FlValue* map); + ~WebResourceRequest() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.cc b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.cc new file mode 100644 index 00000000..09f60fb7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.cc @@ -0,0 +1,39 @@ +#include "web_resource_response.h" + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin { + +WebResourceResponse::WebResourceResponse( + const std::optional& contentType, + const std::optional& contentEncoding, const std::optional& statusCode, + const std::optional& reasonPhrase, + const std::optional>& headers, + const std::optional>& data) + : contentType(contentType), + contentEncoding(contentEncoding), + statusCode(statusCode), + reasonPhrase(reasonPhrase), + headers(headers), + data(data) {} + +WebResourceResponse::WebResourceResponse(FlValue* map) + : contentType(get_optional_fl_map_value(map, "contentType")), + contentEncoding(get_optional_fl_map_value(map, "contentEncoding")), + statusCode(get_optional_fl_map_value(map, "statusCode")), + reasonPhrase(get_optional_fl_map_value(map, "reasonPhrase")), + headers(get_optional_fl_map_value>(map, "headers")), + data(get_optional_fl_map_value>(map, "data")) {} + +FlValue* WebResourceResponse::toFlValue() const { + return to_fl_map({ + {"contentType", make_fl_value(contentType)}, + {"contentEncoding", make_fl_value(contentEncoding)}, + {"statusCode", make_fl_value(statusCode)}, + {"reasonPhrase", make_fl_value(reasonPhrase)}, + {"headers", make_fl_value(headers)}, + {"data", make_fl_value(data)}, + }); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.h b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.h new file mode 100644 index 00000000..6da6bc59 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_resource_response.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ + +#include + +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class WebResourceResponse { + public: + std::optional contentType; + std::optional contentEncoding; + std::optional statusCode; + std::optional reasonPhrase; + std::optional> headers; + std::optional> data; + + WebResourceResponse( + const std::optional& contentType = std::nullopt, + const std::optional& contentEncoding = std::nullopt, + const std::optional& statusCode = std::nullopt, + const std::optional& reasonPhrase = std::nullopt, + const std::optional>& headers = std::nullopt, + const std::optional>& data = std::nullopt); + WebResourceResponse(FlValue* map); + ~WebResourceResponse() = default; + + FlValue* toFlValue() const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.cc b/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.cc new file mode 100644 index 00000000..2965fc50 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.cc @@ -0,0 +1,14 @@ +#include "web_view_transport.h" + +#include "../in_app_webview/in_app_webview.h" + +namespace flutter_inappwebview_plugin { + +WebKitWebView* WebViewTransport::getWebKitWebView() const { + if (inAppWebView) { + return inAppWebView->webview(); + } + return nullptr; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.h b/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.h new file mode 100644 index 00000000..8ceb364a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/types/web_view_transport.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_VIEW_TRANSPORT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_VIEW_TRANSPORT_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class InAppWebView; + +/** + * Holds a WebView created for a new window (via onCreateWindow) + * along with the initial URL request that triggered it. + */ +struct WebViewTransport { + // The InAppWebView wrapper that owns the WebKitWebView + std::unique_ptr inAppWebView; + + // The initial URL request that triggered the window creation + std::optional url; + + WebViewTransport(std::unique_ptr webView, const std::optional& url) + : inAppWebView(std::move(webView)), url(url) {} + + // Get the internal WebKitWebView* (for returning to WebKit create signal) + WebKitWebView* getWebKitWebView() const; + + ~WebViewTransport() = default; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_VIEW_TRANSPORT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/defer.h b/.vendor/flutter_inappwebview_linux/linux/utils/defer.h new file mode 100644 index 00000000..5f47e242 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/defer.h @@ -0,0 +1,48 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ + +#include +#include + +namespace flutter_inappwebview_plugin { + +// Utility to defer cleanup operations using RAII +static inline std::shared_ptr defer(void* handle, + const std::function& callback) { + return std::shared_ptr(handle, callback); +} + +// RAII helper for scoped cleanup +template +class ScopeGuard { + public: + explicit ScopeGuard(Func&& func) : func_(std::move(func)), active_(true) {} + + ScopeGuard(ScopeGuard&& other) noexcept : func_(std::move(other.func_)), active_(other.active_) { + other.dismiss(); + } + + ~ScopeGuard() { + if (active_) { + func_(); + } + } + + void dismiss() noexcept { active_ = false; } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + + private: + Func func_; + bool active_; +}; + +template +ScopeGuard make_scope_guard(Func&& func) { + return ScopeGuard(std::forward(func)); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/flutter.h b/.vendor/flutter_inappwebview_linux/linux/utils/flutter.h new file mode 100644 index 00000000..b9663624 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/flutter.h @@ -0,0 +1,390 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ + +#include + +#include +#include +#include +#include +#include + +#include "map.h" +#include "util.h" +#include "vector.h" + +namespace flutter_inappwebview_plugin { + +// ============================================================================ +// Type aliases for FlValue map building (similar to Windows EncodableMap) +// ============================================================================ + +/** + * Type alias for building FlValue maps using standard C++ initializer lists. + * This allows a consistent pattern similar to Windows' flutter::EncodableMap. + * + * Usage: + * FlValueMap map = { + * {"key1", make_fl_value("value1")}, + * {"key2", make_fl_value(42)}, + * {"key3", make_fl_value(optionalValue)}, // auto handles std::optional + * }; + * return to_fl_map(map); + */ +using FlValueMap = std::initializer_list>; + +/** + * Converts an FlValueMap initializer list to an FlValue* map. + * All values are taken (ownership transferred), so they should be created + * with make_fl_value() or fl_value_new_*() functions. + */ +static inline FlValue* to_fl_map(FlValueMap entries) { + FlValue* map = fl_value_new_map(); + for (const auto& [key, value] : entries) { + fl_value_set_string_take(map, key, value); + } + return map; +} + +// ============================================================================ +// FlValue creation helpers (make_fl_value) +// ============================================================================ + +static inline FlValue* make_fl_value() { + return fl_value_new_null(); +} + +static inline FlValue* make_fl_value(std::nullptr_t) { + return fl_value_new_null(); +} + +static inline FlValue* make_fl_value(bool val) { + return fl_value_new_bool(val); +} + +static inline FlValue* make_fl_value(int32_t val) { + return fl_value_new_int(static_cast(val)); +} + +static inline FlValue* make_fl_value(int64_t val) { + return fl_value_new_int(val); +} + +static inline FlValue* make_fl_value(double val) { + return fl_value_new_float(val); +} + +static inline FlValue* make_fl_value(const char* val) { + return val == nullptr ? fl_value_new_null() : fl_value_new_string(val); +} + +static inline FlValue* make_fl_value(const std::string& val) { + return fl_value_new_string(val.c_str()); +} + +static inline FlValue* make_fl_value(const std::vector& val) { + return fl_value_new_uint8_list(val.data(), val.size()); +} + +template +static inline FlValue* make_fl_value(const std::vector& vec) { + FlValue* list = fl_value_new_list(); + for (const auto& item : vec) { + fl_value_append_take(list, make_fl_value(item)); + } + return list; +} + +template +static inline FlValue* make_fl_value(const std::map& map) { + FlValue* fl_map = fl_value_new_map(); + for (const auto& [key, val] : map) { + fl_value_set_take(fl_map, make_fl_value(key), make_fl_value(val)); + } + return fl_map; +} + +template +static inline FlValue* make_fl_value(const std::optional& optional) { + return optional.has_value() ? make_fl_value(optional.value()) : fl_value_new_null(); +} + +// ============================================================================ +// FlValue map access helpers +// ============================================================================ + +static inline bool fl_map_contains(FlValue* map, const char* key) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return false; + } + FlValue* value = fl_value_lookup_string(map, key); + return value != nullptr; +} + +static inline bool fl_map_contains_not_null(FlValue* map, const char* key) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return false; + } + FlValue* value = fl_value_lookup_string(map, key); + return value != nullptr && fl_value_get_type(value) != FL_VALUE_TYPE_NULL; +} + +// ============================================================================ +// FlValue extraction helpers (get_fl_map_value with default) +// ============================================================================ + +// Generic template declaration +template +static inline T get_fl_map_value(FlValue* map, const char* key, const T& defaultValue); + +// Specialization for bool +template <> +inline bool get_fl_map_value(FlValue* map, const char* key, const bool& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_BOOL) { + return fl_value_get_bool(value); + } + return defaultValue; +} + +// Specialization for int64_t +template <> +inline int64_t get_fl_map_value(FlValue* map, const char* key, + const int64_t& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return fl_value_get_int(value); + } + return defaultValue; +} + +// Specialization for int32_t (uses int64_t internally) +template <> +inline int32_t get_fl_map_value(FlValue* map, const char* key, + const int32_t& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return static_cast(fl_value_get_int(value)); + } + return defaultValue; +} + +// Specialization for double +template <> +inline double get_fl_map_value(FlValue* map, const char* key, const double& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_FLOAT) { + return fl_value_get_float(value); + } + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return static_cast(fl_value_get_int(value)); + } + return defaultValue; +} + +// Specialization for std::string +template <> +inline std::string get_fl_map_value(FlValue* map, const char* key, + const std::string& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_STRING) { + return std::string(fl_value_get_string(value)); + } + return defaultValue; +} + +// Specialization for std::vector +template <> +inline std::vector get_fl_map_value>( + FlValue* map, const char* key, const std::vector& defaultValue) { + if (!fl_map_contains_not_null(map, key)) { + return defaultValue; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) != FL_VALUE_TYPE_LIST) { + return defaultValue; + } + std::vector result; + size_t len = fl_value_get_length(value); + result.reserve(len); + for (size_t i = 0; i < len; i++) { + FlValue* item = fl_value_get_list_value(value, i); + if (fl_value_get_type(item) == FL_VALUE_TYPE_STRING) { + result.push_back(std::string(fl_value_get_string(item))); + } + } + return result; +} + +// ============================================================================ +// FlValue extraction helpers (get_optional_fl_map_value) +// ============================================================================ + +template +static inline std::optional get_optional_fl_map_value(FlValue* map, const char* key); + +// Specialization for bool +template <> +inline std::optional get_optional_fl_map_value(FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_BOOL) { + return std::make_optional(fl_value_get_bool(value)); + } + return std::nullopt; +} + +// Specialization for int64_t +template <> +inline std::optional get_optional_fl_map_value(FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return std::make_optional(fl_value_get_int(value)); + } + return std::nullopt; +} + +// Specialization for int32_t +template <> +inline std::optional get_optional_fl_map_value(FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return std::make_optional(static_cast(fl_value_get_int(value))); + } + return std::nullopt; +} + +// Specialization for double +template <> +inline std::optional get_optional_fl_map_value(FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_FLOAT) { + return std::make_optional(fl_value_get_float(value)); + } + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + return std::make_optional(static_cast(fl_value_get_int(value))); + } + return std::nullopt; +} + +// Specialization for std::string +template <> +inline std::optional get_optional_fl_map_value(FlValue* map, + const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_STRING) { + return std::make_optional(std::string(fl_value_get_string(value))); + } + return std::nullopt; +} + +// Specialization for std::vector +template <> +inline std::optional> get_optional_fl_map_value>( + FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) != FL_VALUE_TYPE_LIST) { + return std::nullopt; + } + std::vector result; + size_t len = fl_value_get_length(value); + result.reserve(len); + for (size_t i = 0; i < len; i++) { + FlValue* item = fl_value_get_list_value(value, i); + if (fl_value_get_type(item) == FL_VALUE_TYPE_STRING) { + result.push_back(std::string(fl_value_get_string(item))); + } + } + return std::make_optional(result); +} + +// Specialization for std::vector +template <> +inline std::optional> get_optional_fl_map_value>( + FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) == FL_VALUE_TYPE_UINT8_LIST) { + size_t len = fl_value_get_length(value); + const uint8_t* data = fl_value_get_uint8_list(value); + return std::make_optional(std::vector(data, data + len)); + } + return std::nullopt; +} + +// Specialization for std::map +template <> +inline std::optional> +get_optional_fl_map_value>(FlValue* map, const char* key) { + if (!fl_map_contains_not_null(map, key)) { + return std::nullopt; + } + FlValue* value = fl_value_lookup_string(map, key); + if (fl_value_get_type(value) != FL_VALUE_TYPE_MAP) { + return std::nullopt; + } + std::map result; + size_t len = fl_value_get_length(value); + for (size_t i = 0; i < len; i++) { + FlValue* map_key = fl_value_get_map_key(value, i); + FlValue* map_val = fl_value_get_map_value(value, i); + if (fl_value_get_type(map_key) == FL_VALUE_TYPE_STRING && + fl_value_get_type(map_val) == FL_VALUE_TYPE_STRING) { + result[fl_value_get_string(map_key)] = fl_value_get_string(map_val); + } + } + return std::make_optional(result); +} + +// ============================================================================ +// FlValue type helpers +// ============================================================================ + +static inline FlValue* fl_value_lookup_string_safe(FlValue* map, const char* key) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return nullptr; + } + return fl_value_lookup_string(map, key); +} + +static inline FlValue* get_fl_map_value_raw(FlValue* map, const char* key) { + return fl_value_lookup_string_safe(map, key); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/gl_context.h b/.vendor/flutter_inappwebview_linux/linux/utils/gl_context.h new file mode 100644 index 00000000..4d51d996 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/gl_context.h @@ -0,0 +1,88 @@ +// GL context utilities for checking if EGL/GLX context is current. +// +// IMPORTANT: We use dlsym to call eglGetCurrentContext/glXGetCurrentContext +// directly from libEGL/libGLX because libepoxy's wrappers go through +// epoxy_get_proc_address which ASSERTS that a context must be current, +// causing a catch-22 situation when we need to check if a context exists. + +#ifndef FLUTTER_INAPPWEBVIEW_LINUX_UTILS_GL_CONTEXT_H_ +#define FLUTTER_INAPPWEBVIEW_LINUX_UTILS_GL_CONTEXT_H_ + +#include +#include + +// EGL_NO_CONTEXT is typically defined as nullptr/0, but we define it here +// to avoid including EGL headers which would conflict with epoxy +#ifndef EGL_NO_CONTEXT +#define EGL_NO_CONTEXT nullptr +#endif + +namespace flutter_inappwebview_plugin { + +/** + * Check if we have a current EGL or GLX context. + * This MUST be called before any GL/EGL operations to avoid crashes from + * libepoxy when no context is current. + * + * @return true if a GL context is current on this thread, false otherwise. + */ +inline bool HasCurrentGLContext() { + // Use cached function pointers to avoid repeated dlsym calls + static void* (*egl_get_current_context)(void) = nullptr; + static void* (*glx_get_current_context)(void) = nullptr; + static bool initialized = false; + + if (!initialized) { + initialized = true; + + // Try to load and get eglGetCurrentContext + // First try NOLOAD (already loaded), then try loading it + void* egl_lib = dlopen("libEGL.so.1", RTLD_NOW | RTLD_NOLOAD); + if (egl_lib == nullptr) { + egl_lib = dlopen("libEGL.so", RTLD_NOW | RTLD_NOLOAD); + } + if (egl_lib == nullptr) { + // Library not loaded yet, try to load it + egl_lib = dlopen("libEGL.so.1", RTLD_NOW); + } + if (egl_lib != nullptr) { + egl_get_current_context = reinterpret_cast( + dlsym(egl_lib, "eglGetCurrentContext")); + } + + // Also try GLX for X11 environments + void* glx_lib = dlopen("libGLX.so.0", RTLD_NOW | RTLD_NOLOAD); + if (glx_lib == nullptr) { + glx_lib = dlopen("libGL.so.1", RTLD_NOW | RTLD_NOLOAD); + } + if (glx_lib == nullptr) { + glx_lib = dlopen("libGL.so", RTLD_NOW | RTLD_NOLOAD); + } + if (glx_lib != nullptr) { + glx_get_current_context = reinterpret_cast( + dlsym(glx_lib, "glXGetCurrentContext")); + } + } + + // Check EGL context first + if (egl_get_current_context != nullptr) { + void* ctx = egl_get_current_context(); + if (ctx != nullptr && ctx != EGL_NO_CONTEXT) { + return true; + } + } + + // Check GLX context + if (glx_get_current_context != nullptr) { + void* ctx = glx_get_current_context(); + if (ctx != nullptr) { + return true; + } + } + + return false; +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_LINUX_UTILS_GL_CONTEXT_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/log.h b/.vendor/flutter_inappwebview_linux/linux/utils/log.h new file mode 100644 index 00000000..ec6b9578 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/log.h @@ -0,0 +1,110 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ + +#include + +#include +#include +#include + +#include "string.h" + +namespace flutter_inappwebview_plugin { + +template +static inline void debugLog(const std::basic_string& msg, const bool& isError = false, + const std::string& filename = "", const int& line = 0) { +#ifndef NDEBUG + std::basic_string debugMsg = msg; + if (!filename.empty() && line > 0) { + auto filenameSplit = split(filename, std::string{"/flutter_inappwebview_linux/"}); + std::string reduceFilenamePath = + filenameSplit.size() > 0 ? "flutter_inappwebview_linux/" + filenameSplit.back() : filename; + debugMsg = reduceFilenamePath + "(" + std::to_string(line) + "): " + debugMsg; + } + + // Use g_message for GLib-based logging + if (isError) { + g_warning("[flutter_inappwebview] %s", std::string(debugMsg).c_str()); + } else { + g_message("[flutter_inappwebview] %s", std::string(debugMsg).c_str()); + } +#endif +} + +static inline void debugLog(const char* msg, const bool& isError = false, + const std::string& filename = "", const int& line = 0) { + debugLog(std::string(msg), isError, filename, line); +} + +static inline void debugLog(const bool& value, const bool& isError = false, + const std::string& filename = "", const int& line = 0) { + debugLog(value ? "true" : "false", isError, filename, line); +} + +template ::value, T>::type> +static inline void debugLog(const T& value, const bool& isError = false, + const std::string& filename = "", const int& line = 0) { + debugLog(std::to_string(value), isError, filename, line); +} + +static inline void errorLog(const std::string& msg, const std::string& filename = "", + const int& line = 0) { + debugLog(msg, true, filename, line); +} + +// GError logging helper +static inline void logGError(GError* error, const std::string& filename = "", const int& line = 0) { + if (error != nullptr) { + std::string msg = "GError: " + std::string(error->message) + + " (domain: " + std::to_string(error->domain) + + ", code: " + std::to_string(error->code) + ")"; + debugLog(msg, true, filename, line); + } +} + +// Success check with logging for GError - returns true if NO error +static inline bool succeededOrLog(GError** error, const std::string& filename = "", + const int& line = 0) { + if (error != nullptr && *error != nullptr) { + logGError(*error, filename, line); + g_error_free(*error); + *error = nullptr; + return false; + } + return true; +} + +// Failed check with logging for GError - returns true if there IS an error +static inline bool failedAndLog(GError** error, const std::string& filename = "", + const int& line = 0) { + if (error != nullptr && *error != nullptr) { + logGError(*error, filename, line); + g_error_free(*error); + *error = nullptr; + return true; + } + return false; +} + +// Log GError without returning status +static inline void failedLog(GError** error, const std::string& filename = "", + const int& line = 0) { + if (error != nullptr && *error != nullptr) { + logGError(*error, filename, line); + g_error_free(*error); + *error = nullptr; + } +} + +} // namespace flutter_inappwebview_plugin + +#ifndef NDEBUG +#define debugLog(value) debugLog(value, false, __FILE__, __LINE__) +#define logGError(error) logGError(error, __FILE__, __LINE__) +#define succeededOrLog(error) succeededOrLog(error, __FILE__, __LINE__) +#define failedAndLog(error) failedAndLog(error, __FILE__, __LINE__) +#define failedLog(error) failedLog(error, __FILE__, __LINE__) +#endif + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/map.h b/.vendor/flutter_inappwebview_linux/linux/utils/map.h new file mode 100644 index 00000000..1c10b745 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/map.h @@ -0,0 +1,42 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +template +struct is_mappish_impl : std::false_type {}; + +template +struct is_mappish_impl< + T, std::void_t()[std::declval()])>> + : std::true_type {}; + +template +struct is_mappish : is_mappish_impl::type {}; + +template +static inline bool map_contains(const std::map& map, const K& key) { + return map.find(key) != map.end(); +} + +template +static inline T map_at_or_null(const std::map& map, const K& key) { + auto itr = map.find(key); + return itr != map.end() ? itr->second : nullptr; +} + +template +static inline std::optional map_at_optional(const std::map& map, const K& key) { + auto itr = map.find(key); + return itr != map.end() ? std::make_optional(itr->second) : std::nullopt; +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.cc b/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.cc new file mode 100644 index 00000000..c549e801 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.cc @@ -0,0 +1,200 @@ +// Utility to detect if software rendering should be used +// This checks for VM environments where DMA-BUF/GPU acceleration may not work properly + +#include "software_rendering.h" + +#include +#include +#include +#include +#include + +#include "log.h" + +namespace flutter_inappwebview_plugin { + +namespace { + +// Check if we're running in a virtual machine by reading system files. +// This is a standard approach used by systemd-detect-virt, virt-what, etc. +// These files are readable by any process and contain only hardware ID info. +bool IsRunningInVirtualMachine() { + // Known VM product name indicators + const char* vm_indicators[] = { + "QEMU", "KVM", "VMware", "VirtualBox", "Parallels", "Xen", + "Microsoft Virtual", "Hyper-V", "UTM", "Virtual Machine", + "Bochs", "innotek", "Oracle", nullptr + }; + + // Check /sys/class/dmi/id/product_name + FILE* f = fopen("/sys/class/dmi/id/product_name", "r"); + if (f) { + char buf[256] = {0}; + if (fgets(buf, sizeof(buf), f)) { + fclose(f); + for (const char** indicator = vm_indicators; *indicator; indicator++) { + if (strcasestr(buf, *indicator)) { + debugLog("VM detected via product_name: " + std::string(buf)); + return true; + } + } + } else { + fclose(f); + } + } + + // Check /sys/class/dmi/id/sys_vendor + f = fopen("/sys/class/dmi/id/sys_vendor", "r"); + if (f) { + char buf[256] = {0}; + if (fgets(buf, sizeof(buf), f)) { + fclose(f); + for (const char** indicator = vm_indicators; *indicator; indicator++) { + if (strcasestr(buf, *indicator)) { + debugLog("VM detected via sys_vendor: " + std::string(buf)); + return true; + } + } + } else { + fclose(f); + } + } + + // Check /proc/cpuinfo for hypervisor flag (x86/x64) + f = fopen("/proc/cpuinfo", "r"); + if (f) { + char line[512]; + while (fgets(line, sizeof(line), f)) { + if (strstr(line, "flags") && strstr(line, "hypervisor")) { + fclose(f); + debugLog("VM detected via hypervisor CPU flag"); + return true; + } + } + fclose(f); + } + + return false; +} + +// Check if we have a known good GPU driver that works well with DMA-BUF +bool HasKnownGoodGpuDriver() { + const char* dri_paths[] = { + "/sys/class/drm/card0/device/driver", + "/sys/class/drm/renderD128/device/driver", + nullptr + }; + + // Known good drivers that work well with DMA-BUF + const char* good_drivers[] = { + "nvidia", // Nvidia proprietary driver + "nouveau", // Nvidia open-source driver + "amdgpu", // AMD GPU driver + "radeon", // Older AMD driver + "i915", // Intel integrated graphics + "xe", // Intel Xe graphics (newer) + nullptr + }; + + for (const char** path = dri_paths; *path; path++) { + char link_target[256] = {0}; + ssize_t len = readlink(*path, link_target, sizeof(link_target) - 1); + if (len > 0) { + link_target[len] = '\0'; + for (const char** driver = good_drivers; *driver; driver++) { + if (strstr(link_target, *driver)) { + debugLog("Known good GPU driver detected: " + std::string(link_target)); + return true; + } + } + } + } + + return false; +} + +// Check if the GPU driver is known to have DMA-BUF issues +bool HasProblematicGpuDriver() { + // If we have a known good driver, it's not problematic + if (HasKnownGoodGpuDriver()) { + return false; + } + + const char* dri_paths[] = { + "/sys/class/drm/card0/device/driver", + "/sys/class/drm/renderD128/device/driver", + nullptr + }; + + for (const char** path = dri_paths; *path; path++) { + char link_target[256] = {0}; + ssize_t len = readlink(*path, link_target, sizeof(link_target) - 1); + if (len > 0) { + link_target[len] = '\0'; + // virtio-gpu and vmwgfx have known issues in VMs + if (strstr(link_target, "virtio") || strstr(link_target, "vmwgfx")) { + debugLog("Problematic GPU driver detected: " + std::string(link_target)); + return true; + } + } + } + + return false; +} + +} // namespace + +bool ShouldUseSoftwareRendering() { + // Check user override: skip detection + const char* skip_check = getenv("FLUTTER_INAPPWEBVIEW_SKIP_DMABUF_CHECK"); + if (skip_check && (strcmp(skip_check, "1") == 0 || strcasecmp(skip_check, "true") == 0)) { + return false; + } + + // Already set by user or another component + const char* already_sw = getenv("LIBGL_ALWAYS_SOFTWARE"); + if (already_sw && (strcmp(already_sw, "1") == 0 || strcasecmp(already_sw, "true") == 0)) { + return true; // Already in software mode + } + + // If we have a known good GPU driver, use hardware rendering + if (HasKnownGoodGpuDriver()) { + debugLog("Known good GPU driver found, using hardware rendering"); + return false; + } + + const bool in_vm = IsRunningInVirtualMachine(); + + // Detect VM environment + if (in_vm) { + return true; + } + + // Detect problematic GPU drivers only inside a VM + if (in_vm && HasProblematicGpuDriver()) { + return true; + } + + return false; +} + +bool ApplySoftwareRenderingIfNeeded() { + // Already set - nothing to do + const char* already_sw = getenv("LIBGL_ALWAYS_SOFTWARE"); + if (already_sw && (strcmp(already_sw, "1") == 0 || strcasecmp(already_sw, "true") == 0)) { + debugLog("Software rendering already enabled (LIBGL_ALWAYS_SOFTWARE set)"); + return true; + } + + if (ShouldUseSoftwareRendering()) { + // Set BEFORE any EGL/GL initialization + setenv("LIBGL_ALWAYS_SOFTWARE", "1", 0); // Don't override if already set + debugLog("Auto-enabled software rendering for VM/problematic GPU environment"); + return true; + } + + debugLog("Using hardware GPU rendering"); + return false; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.h b/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.h new file mode 100644 index 00000000..b51e2741 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/software_rendering.h @@ -0,0 +1,28 @@ +// Utility to detect if software rendering should be used +// This checks for VM environments where DMA-BUF/GPU acceleration may not work properly + +#ifndef FLUTTER_INAPPWEBVIEW_LINUX_UTILS_SOFTWARE_RENDERING_H_ +#define FLUTTER_INAPPWEBVIEW_LINUX_UTILS_SOFTWARE_RENDERING_H_ + +namespace flutter_inappwebview_plugin { + +// Check if software rendering should be automatically enabled. +// This detects VM environments (UTM, QEMU, VMware, VirtualBox, etc.) where +// GPU acceleration via DMA-BUF may not work correctly. +// +// Environment variables: +// - LIBGL_ALWAYS_SOFTWARE=1 : Force software rendering (standard WebKit flag) +// - FLUTTER_INAPPWEBVIEW_SKIP_DMABUF_CHECK=1 : Skip detection, use hardware +// +// If this returns true, LIBGL_ALWAYS_SOFTWARE=1 should be set BEFORE any +// EGL/GL/WPE initialization to ensure WebKit uses SHM buffers. +bool ShouldUseSoftwareRendering(); + +// Apply software rendering environment if needed. +// Call this ONCE at plugin initialization, BEFORE any WebView is created. +// Returns true if software rendering was enabled. +bool ApplySoftwareRenderingIfNeeded(); + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_LINUX_UTILS_SOFTWARE_RENDERING_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/string.h b/.vendor/flutter_inappwebview_linux/linux/utils/string.h new file mode 100644 index 00000000..a2796d08 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/string.h @@ -0,0 +1,186 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +template +struct is_string : std::false_type {}; + +// Partial specialization - parameters used to qualify the specialization +template +struct is_string> : std::true_type {}; + +template +using is_basic_string = is_string>; + +template +static inline bool string_equals(const std::basic_string& s1, const std::basic_string& s2) { + return s1.compare(s2) == 0; +} + +template +static inline bool string_equals(const std::basic_string& s1, const char* s2) { + return s1.compare(s2) == 0; +} + +template +static inline bool string_equals(const char* s1, const std::basic_string& s2) { + return s2.compare(s1) == 0; +} + +template +static inline bool string_equals(const std::optional>& s1, + const std::basic_string& s2) { + return s1.has_value() ? string_equals(s1.value(), s2) : false; +} + +template +static inline bool string_equals(const std::basic_string& s1, + const std::optional>& s2) { + return s2.has_value() ? string_equals(s1, s2.value()) : false; +} + +template +static inline bool string_equals(const std::optional>& s1, + const std::optional>& s2) { + return s1.has_value() && s2.has_value() ? string_equals(s1.value(), s2.value()) : true; +} + +static inline void replace_all(std::string& source, const std::string& from, + const std::string& to) { + std::string newString; + newString.reserve(source.length()); // avoids a few memory allocations + + std::string::size_type lastPos = 0; + std::string::size_type findPos; + + while (std::string::npos != (findPos = source.find(from, lastPos))) { + newString.append(source, lastPos, findPos - lastPos); + newString += to; + lastPos = findPos + from.length(); + } + + // Care for the rest after last occurrence + newString += source.substr(lastPos); + + source.swap(newString); +} + +static inline std::string replace_all_copy(const std::string& source, const std::string& from, + const std::string& to) { + std::string newString; + newString.reserve(source.length()); // avoids a few memory allocations + + std::string::size_type lastPos = 0; + std::string::size_type findPos; + + while (std::string::npos != (findPos = source.find(from, lastPos))) { + newString.append(source, lastPos, findPos - lastPos); + newString += to; + lastPos = findPos + from.length(); + } + + // Care for the rest after last occurrence + newString += source.substr(lastPos); + + return newString; +} + +template +static inline std::basic_string join(const std::vector>& vec, + const std::basic_string& delim) { + return vec.empty() ? std::basic_string{""} + : std::accumulate(++vec.begin(), vec.end(), *vec.begin(), + [&delim](auto& a, auto& b) { return a + delim + b; }); +} + +template +static inline std::basic_string join(const std::vector>& vec, + const char* delim) { + return join(vec, std::basic_string{delim}); +} + +template +static inline std::vector> split(const std::basic_string& s, + std::basic_string delimiter) { + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::basic_string token; + std::vector> res; + + while ((pos_end = s.find(delimiter, pos_start)) != std::basic_string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(s.substr(pos_start)); + return res; +} + +template +void to_lowercase(std::basic_string& s) { + std::transform(s.begin(), s.end(), s.begin(), + [](const T v) { return static_cast(std::tolower(v)); }); +} + +template +std::basic_string to_lowercase_copy(const std::basic_string& s) { + std::basic_string s2 = s; + std::transform(s2.begin(), s2.end(), s2.begin(), + [](const T v) { return static_cast(std::tolower(v)); }); + return s2; +} + +template +void to_uppercase(std::basic_string& s) { + std::transform(s.begin(), s.end(), s.begin(), + [](const T v) { return static_cast(std::toupper(v)); }); +} + +template +std::basic_string to_uppercase_copy(const std::basic_string& s) { + std::basic_string s2 = s; + std::transform(s2.begin(), s2.end(), s2.begin(), + [](const T v) { return static_cast(std::toupper(v)); }); + return s2; +} + +template +bool starts_with(const std::basic_string& str, const std::basic_string& prefix) { + return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; +} + +template +bool ends_with(const std::basic_string& str, const std::basic_string& suffix) { + return str.size() >= suffix.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +constexpr uint32_t string_hash(const std::string_view data) noexcept { + uint32_t hash = 5381; + for (const auto& e : data) + hash = ((hash << 5) + hash) + e; + return hash; +}; + +static inline std::string trim(const std::string& str) { + size_t first = str.find_first_not_of(" \t\n\r\f\v"); + if (std::string::npos == first) { + return str; + } + size_t last = str.find_last_not_of(" \t\n\r\f\v"); + return str.substr(first, (last - first + 1)); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/uri.h b/.vendor/flutter_inappwebview_linux/linux/utils/uri.h new file mode 100644 index 00000000..bdf89570 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/uri.h @@ -0,0 +1,126 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ + +#include +#include +#include + +#include "string.h" + +namespace flutter_inappwebview_plugin { + +// Extract origin (scheme + host + port) from a URL +static inline std::string get_origin_from_url(const std::string& url) { + // Try to parse using regex for common URL formats + try { + // Pattern: scheme://host[:port][/path...] + std::regex url_regex(R"(^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^:\/\?#]+)(?::(\d+))?)", + std::regex::ECMAScript); + std::smatch match; + + if (std::regex_search(url, match, url_regex)) { + std::string scheme = match[1].str(); + std::string host = match[2].str(); + std::string port_str = match[3].str(); + + std::string origin = scheme + "://" + host; + + if (!port_str.empty()) { + int port = std::stoi(port_str); + // Only add port if it's not the default for the scheme + bool isDefaultPort = (string_equals(scheme, "http") && port == 80) || + (string_equals(scheme, "https") && port == 443); + if (!isDefaultPort) { + origin += ":" + port_str; + } + } + + return origin; + } + } catch (const std::regex_error&) { + // Fallback to simple string parsing + } + + // Fallback: simple string parsing + auto urlSplit = split(url, std::string{"://"}); + if (urlSplit.size() > 1) { + auto scheme = urlSplit[0]; + auto afterScheme = urlSplit[1]; + auto afterSchemeSplit = split(afterScheme, std::string{"/"}); + auto hostPort = afterSchemeSplit[0]; + + // Further split to remove query string if present + auto hostPortSplit = split(hostPort, std::string{"?"}); + hostPort = hostPortSplit[0]; + + return scheme + "://" + hostPort; + } + + return url; +} + +// Check if a URL is valid +static inline bool is_valid_url(const std::string& url) { + if (url.empty()) { + return false; + } + + try { + std::regex url_regex(R"(^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s]+$)", std::regex::ECMAScript); + return std::regex_match(url, url_regex); + } catch (const std::regex_error&) { + // Fallback: check for basic URL structure + return url.find("://") != std::string::npos; + } +} + +// Extract scheme from URL +static inline std::string get_scheme_from_url(const std::string& url) { + auto pos = url.find("://"); + if (pos != std::string::npos) { + return url.substr(0, pos); + } + return ""; +} + +// Extract host from URL +static inline std::string get_host_from_url(const std::string& url) { + auto urlSplit = split(url, std::string{"://"}); + if (urlSplit.size() > 1) { + auto afterScheme = urlSplit[1]; + auto afterSchemeSplit = split(afterScheme, std::string{"/"}); + auto hostPort = afterSchemeSplit[0]; + + // Remove port if present + auto colonPos = hostPort.find(':'); + if (colonPos != std::string::npos) { + return hostPort.substr(0, colonPos); + } + return hostPort; + } + return ""; +} + +// URL encode a string +static inline std::string url_encode(const std::string& value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + // Keep alphanumeric and other accepted characters intact + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || + c == '~') { + escaped << c; + } else { + // Percent-encode other characters + escaped << '%' << std::setw(2) << static_cast(static_cast(c)); + } + } + + return escaped.str(); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/util.h b/.vendor/flutter_inappwebview_linux/linux/utils/util.h new file mode 100644 index 00000000..95210c5a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/util.h @@ -0,0 +1,128 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ + +#include +#include + +#include +#include +#include +#include + +#include + +namespace flutter_inappwebview_plugin { + +// Helper for static_assert in constexpr if +template +inline constexpr bool always_false_v = false; + +template +static inline std::optional make_pointer_optional(const T* value) { + return value == nullptr ? std::nullopt : std::make_optional(*value); +} + +static inline std::string variant_to_string(const std::variant& var) { + return std::visit( + [](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + return arg; + else if constexpr (std::is_arithmetic_v) + return std::to_string(arg); + else + static_assert(always_false_v, "non-exhaustive visitor!"); + }, + var); +} + +// === Application ID Utilities === +// Used for creating app-specific storage paths (credentials, content filters, etc.) + +static inline bool is_allowed_app_id_char(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || + c == '.' || c == '_' || c == '-'; +} + +static inline std::string sanitize_app_id(std::string id) { + std::string out; + out.reserve(id.size()); + + bool last_was_underscore = false; + for (char c : id) { + char mapped = is_allowed_app_id_char(c) ? c : '_'; + if (mapped == '_' && last_was_underscore) { + continue; + } + out.push_back(mapped); + last_was_underscore = (mapped == '_'); + } + + // Trim underscores. + while (!out.empty() && out.front() == '_') { + out.erase(out.begin()); + } + while (!out.empty() && out.back() == '_') { + out.pop_back(); + } + + if (out.size() > 128) { + out.resize(128); + } + if (out.empty()) { + out = "unknown_app"; + } + return out; +} + +static inline std::string resolve_app_id_from_gapplication() { + GApplication* app = g_application_get_default(); + if (app == nullptr) { + return ""; + } + const gchar* id = g_application_get_application_id(app); + if (id == nullptr || id[0] == '\0') { + return ""; + } + return std::string(id); +} + +static inline std::string basename_of_path(const std::string& path) { + if (path.empty()) { + return ""; + } + size_t last_slash = path.find_last_of('/'); + if (last_slash == std::string::npos) { + return path; + } + if (last_slash + 1 >= path.size()) { + return ""; + } + return path.substr(last_slash + 1); +} + +static inline std::string resolve_app_id_from_executable_basename() { + char buf[PATH_MAX + 1]; + ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX); + if (len <= 0) { + return ""; + } + buf[len] = '\0'; + std::string exe_path(buf); + + return basename_of_path(exe_path); +} + +/// Resolves a sanitized application ID for use in storage paths. +/// Tries GApplication ID first, falls back to executable name. +static inline std::string resolve_application_id_sanitized() { + std::string raw = resolve_app_id_from_gapplication(); + if (raw.empty()) { + raw = resolve_app_id_from_executable_basename(); + } + return sanitize_app_id(raw); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/uuid.h b/.vendor/flutter_inappwebview_linux/linux/utils/uuid.h new file mode 100644 index 00000000..9626b429 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/uuid.h @@ -0,0 +1,34 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +// Generate a UUID v4 string using standard C++ random +static inline std::string get_uuid() { + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution dis(0, 0xFFFFFFFF); + + uint32_t data1 = dis(gen); + uint16_t data2 = static_cast(dis(gen) & 0xFFFF); + uint16_t data3 = static_cast((dis(gen) & 0x0FFF) | 0x4000); // Version 4 + uint16_t data4 = static_cast((dis(gen) & 0x3FFF) | 0x8000); // Variant 1 + uint32_t data5_hi = dis(gen); + uint16_t data5_lo = static_cast(dis(gen) & 0xFFFF); + + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(8) << data1 << "-" << std::setw(4) << data2 + << "-" << std::setw(4) << data3 << "-" << std::setw(4) << data4 << "-" << std::setw(8) + << data5_hi << std::setw(4) << data5_lo; + + return oss.str(); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/utils/vector.h b/.vendor/flutter_inappwebview_linux/linux/utils/vector.h new file mode 100644 index 00000000..cb8f4c38 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/utils/vector.h @@ -0,0 +1,92 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ + +#include +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +template +struct is_vector_impl : std::false_type {}; + +template +struct is_vector_impl>::value>> + : std::true_type {}; + +template +struct is_vector_impl< + T, std::enable_if_t::value_type>::iterator>::value>> + : std::true_type {}; + +template +struct is_vector : is_vector_impl::type {}; + +template +static inline void vector_remove(std::vector& vec, const T& el) { + vec.erase(std::remove(vec.begin(), vec.end(), el), vec.end()); +} + +template +static inline void vector_remove_if(std::vector& vec, UnaryPredicate&& predicate) { + vec.erase(std::remove_if(vec.begin(), vec.end(), std::forward(predicate)), + vec.end()); +} + +template +static inline void vector_remove_erase(std::vector& vec, const T& el) { + vec.erase(std::remove(vec.begin(), vec.end(), el), vec.end()); +} + +template +static inline void vector_remove_erase_if(std::vector& vec, UnaryPredicate&& predicate) { + vec.erase(std::remove_if(vec.begin(), vec.end(), std::forward(predicate)), + vec.end()); +} + +template +static inline bool vector_contains(const std::vector& vec, const T& value) { + return std::find(vec.begin(), vec.end(), value) != vec.end(); +} + +template +static inline bool vector_contains_if(const std::vector& vec, UnaryPredicate&& predicate) { + return std::find_if(vec.begin(), vec.end(), std::forward(predicate)) != vec.end(); +} + +template +static inline auto functional_map(Iterator begin, Iterator end, Func&& func) + -> std::vector()))> { + using value_type = decltype(func(std::declval())); + + std::vector out_vector; + out_vector.reserve(std::distance(begin, end)); + + std::transform(begin, end, std::back_inserter(out_vector), std::forward(func)); + + return out_vector; +} + +template +static inline auto functional_map(const T& iterable, Func&& func) + -> std::vector()))> { + return functional_map(std::begin(iterable), std::end(iterable), std::forward(func)); +} + +template +static inline auto functional_map(const std::optional& iterable, Func&& func) + -> std::vector()))> { + if (!iterable.has_value()) { + return {}; + } + return functional_map(iterable.value(), std::forward(func)); +} + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.cc b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.cc new file mode 100644 index 00000000..7763fbd7 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.cc @@ -0,0 +1,151 @@ +#include "web_message_channel.h" + +#include + +#include "../in_app_webview/in_app_webview.h" +#include "../utils/flutter.h" +#include "../utils/log.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +WebMessageChannel::WebMessageChannel(FlBinaryMessenger* messenger, + const std::string& channelId, + InAppWebView* webView) + : ChannelDelegate(messenger, std::string(METHOD_CHANNEL_NAME_PREFIX) + channelId), + id_(channelId), + webView_(webView) { +} + +WebMessageChannel::~WebMessageChannel() { + debugLog("dealloc WebMessageChannel"); + webView_ = nullptr; +} + +void WebMessageChannel::dispose() { + unregisterMethodCallHandler(); + webView_ = nullptr; +} + +void WebMessageChannel::HandleMethodCall(FlMethodCall* method_call) { + if (webView_ == nullptr) { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + return; + } + + const gchar* methodName = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(methodName, "setWebMessageCallback")) { + // Set the onmessage callback for a port + int64_t portIndex = get_fl_map_value(args, "index", 0); + + webView_->setWebMessageCallback(id_, static_cast(portIndex)); + + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (string_equals(methodName, "postMessage")) { + // Post a message on a port + int64_t portIndex = get_fl_map_value(args, "index", 0); + FlValue* message_value = get_fl_map_value_raw(args, "message"); + + std::string messageData = ""; + int64_t messageType = 0; // 0 = string, 1 = arrayBuffer + + if (message_value != nullptr && fl_value_get_type(message_value) == FL_VALUE_TYPE_MAP) { + FlValue* data_value = fl_value_lookup_string(message_value, "data"); + FlValue* type_value = fl_value_lookup_string(message_value, "type"); + + if (data_value != nullptr) { + if (fl_value_get_type(data_value) == FL_VALUE_TYPE_STRING) { + messageData = fl_value_get_string(data_value); + } else if (fl_value_get_type(data_value) == FL_VALUE_TYPE_UINT8_LIST) { + // Convert bytes to comma-separated values for JavaScript + const uint8_t* bytes = fl_value_get_uint8_list(data_value); + size_t length = fl_value_get_length(data_value); + for (size_t i = 0; i < length; i++) { + if (i > 0) messageData += ","; + messageData += std::to_string(bytes[i]); + } + } + } + if (type_value != nullptr && fl_value_get_type(type_value) == FL_VALUE_TYPE_INT) { + messageType = fl_value_get_int(type_value); + } + } + + webView_->postWebMessageOnPort(id_, static_cast(portIndex), messageData, messageType); + + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + if (string_equals(methodName, "close")) { + // Close a port + int64_t portIndex = get_fl_map_value(args, "index", 0); + + webView_->closeWebMessagePort(id_, static_cast(portIndex)); + + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void WebMessageChannel::onMessage(int portIndex, const std::string* message, + int64_t messageType) { + if (channel_ == nullptr) { + return; + } + + FlValue* messageMap = nullptr; + if (message != nullptr) { + FlValue* dataValue = nullptr; + if (messageType == 1) { + // ArrayBuffer - convert comma-separated values to byte array + std::vector bytes; + std::string value; + for (char c : *message) { + if (c == ',') { + if (!value.empty()) { + bytes.push_back(static_cast(std::stoi(value))); + value.clear(); + } + } else { + value += c; + } + } + if (!value.empty()) { + bytes.push_back(static_cast(std::stoi(value))); + } + dataValue = fl_value_new_uint8_list(bytes.data(), bytes.size()); + } else { + dataValue = make_fl_value(*message); + } + messageMap = to_fl_map({ + {"data", dataValue}, + {"type", make_fl_value(messageType)}, + }); + } + + g_autoptr(FlValue) args = to_fl_map({ + {"index", make_fl_value(portIndex)}, + {"message", messageMap != nullptr ? messageMap : fl_value_new_null()}, + }); + + invokeMethod("onMessage", args); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.h b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.h new file mode 100644 index 00000000..590833fd --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_channel.h @@ -0,0 +1,58 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_H_ + +#include + +#include +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class InAppWebView; + +/** + * Native C++ representation of a WebMessageChannel. + * + * Each WebMessageChannel has its own MethodChannel for communication + * with the Dart side. This allows the Dart side to call port-specific + * methods like setWebMessageCallback, postMessage, and close. + */ +class WebMessageChannel : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME_PREFIX = + "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"; + + WebMessageChannel(FlBinaryMessenger* messenger, const std::string& channelId, + InAppWebView* webView); + ~WebMessageChannel(); + + const std::string& id() const { return id_; } + + /** + * Dispose of this WebMessageChannel, releasing resources and unregistering + * the method call handler. + */ + void dispose(); + + /** + * Send a message to the Dart side on a specific port. + * + * @param portIndex The port index (0 or 1) + * @param message The message data (may be null) + * @param messageType 0 for string, 1 for arrayBuffer + */ + void onMessage(int portIndex, const std::string* message, int64_t messageType); + + // ChannelDelegate override + void HandleMethodCall(FlMethodCall* method_call) override; + + private: + std::string id_; + InAppWebView* webView_; // Weak reference - InAppWebView owns WebMessageChannels +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_CHANNEL_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.cc b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.cc new file mode 100644 index 00000000..cab66e9a --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.cc @@ -0,0 +1,203 @@ +#include "web_message_listener.h" + +#include +#include +#include + +#include "../in_app_webview/in_app_webview.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "web_message_listener_channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +std::unique_ptr WebMessageListener::fromFlValue( + FlBinaryMessenger* messenger, + FlValue* map, + InAppWebView* webView) { + if (map == nullptr || fl_value_get_type(map) != FL_VALUE_TYPE_MAP) { + return nullptr; + } + + std::string id = get_fl_map_value(map, "id", ""); + std::string jsObjectName = get_fl_map_value(map, "jsObjectName", ""); + + std::set allowedOriginRules; + FlValue* rules_value = fl_value_lookup_string(map, "allowedOriginRules"); + if (rules_value != nullptr && fl_value_get_type(rules_value) == FL_VALUE_TYPE_LIST) { + size_t length = fl_value_get_length(rules_value); + for (size_t i = 0; i < length; i++) { + FlValue* rule = fl_value_get_list_value(rules_value, i); + if (rule != nullptr && fl_value_get_type(rule) == FL_VALUE_TYPE_STRING) { + allowedOriginRules.insert(fl_value_get_string(rule)); + } + } + } + + if (id.empty() || jsObjectName.empty()) { + errorLog("WebMessageListener::fromFlValue: missing id or jsObjectName"); + return nullptr; + } + + return std::make_unique( + messenger, id, jsObjectName, allowedOriginRules, webView); +} + +WebMessageListener::WebMessageListener(FlBinaryMessenger* messenger, + const std::string& id, + const std::string& jsObjectName, + const std::set& allowedOriginRules, + InAppWebView* webView) + : id_(id), + jsObjectName_(jsObjectName), + allowedOriginRules_(allowedOriginRules), + webView_(webView) { + // Create the channel name following the pattern: + // com.pichillilorenzo/flutter_inappwebview_web_message_listener_{id}_{jsObjectName} + std::string channelName = std::string(METHOD_CHANNEL_NAME_PREFIX) + id + "_" + jsObjectName; + + channelDelegate_ = std::make_unique( + this, messenger, channelName); +} + +WebMessageListener::~WebMessageListener() { + debugLog("dealloc WebMessageListener"); + dispose(); +} + +bool WebMessageListener::isOriginAllowed(const std::string& scheme, + const std::string& host, + int port) const { + for (const auto& rule : allowedOriginRules_) { + // Wildcard matches all origins + if (rule == "*") { + return true; + } + + // Skip empty rules + if (rule.empty()) { + continue; + } + + // Parse the rule: scheme://host[:port] + size_t schemeEnd = rule.find("://"); + if (schemeEnd == std::string::npos) { + continue; + } + + std::string ruleScheme = rule.substr(0, schemeEnd); + std::string rest = rule.substr(schemeEnd + 3); + + std::string ruleHost; + int rulePort = 0; + + size_t portStart = rest.find(':'); + if (portStart != std::string::npos) { + ruleHost = rest.substr(0, portStart); + try { + rulePort = std::stoi(rest.substr(portStart + 1)); + } catch (...) { + rulePort = 0; + } + } else { + ruleHost = rest; + } + + // Normalize ports (use default for scheme if not specified) + int normalizedRulePort = rulePort; + if (normalizedRulePort == 0) { + normalizedRulePort = (ruleScheme == "https") ? 443 : 80; + } + + int normalizedPort = port; + if (normalizedPort == 0) { + normalizedPort = (scheme == "https") ? 443 : 80; + } + + // Check scheme match + if (scheme != ruleScheme) { + continue; + } + + // Check port match + if (normalizedPort != normalizedRulePort) { + continue; + } + + // Check host match (including wildcard subdomain matching) + if (ruleHost.empty() || host == ruleHost) { + return true; + } + + // Handle wildcard subdomain matching: *.example.com + if (ruleHost.size() > 2 && ruleHost[0] == '*' && ruleHost[1] == '.') { + std::string suffix = ruleHost.substr(1); // .example.com + if (host.size() > suffix.size() && + host.compare(host.size() - suffix.size(), suffix.size(), suffix) == 0) { + return true; + } + } + } + + return false; +} + +void WebMessageListener::onPostMessage(const std::string* messageData, + int64_t messageType, + const std::string& sourceOrigin, + bool isMainFrame) { + if (channelDelegate_ == nullptr) { + return; + } + + // Parse the origin to check if it's allowed + std::string scheme; + std::string host; + int port = 0; + + if (!sourceOrigin.empty()) { + size_t schemeEnd = sourceOrigin.find("://"); + if (schemeEnd != std::string::npos) { + scheme = sourceOrigin.substr(0, schemeEnd); + std::string rest = sourceOrigin.substr(schemeEnd + 3); + + size_t portStart = rest.find(':'); + size_t pathStart = rest.find('/'); + + if (portStart != std::string::npos && (pathStart == std::string::npos || portStart < pathStart)) { + host = rest.substr(0, portStart); + size_t portEnd = (pathStart != std::string::npos) ? pathStart : rest.size(); + try { + port = std::stoi(rest.substr(portStart + 1, portEnd - portStart - 1)); + } catch (...) { + port = 0; + } + } else if (pathStart != std::string::npos) { + host = rest.substr(0, pathStart); + } else { + host = rest; + } + } + } + + // Check if this origin is allowed (unless origin is empty/about:blank) + if (!sourceOrigin.empty() && sourceOrigin != "null") { + if (!isOriginAllowed(scheme, host, port)) { + debugLog("WebMessageListener: Origin not allowed: " + sourceOrigin); + return; + } + } + + // Invoke the callback on the Dart side via the dedicated channel + channelDelegate_->onPostMessage(messageData, messageType, &sourceOrigin, isMainFrame); +} + +void WebMessageListener::dispose() { + if (channelDelegate_) { + channelDelegate_->dispose(); + channelDelegate_.reset(); + } + webView_ = nullptr; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.h b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.h new file mode 100644 index 00000000..b5a105a2 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener.h @@ -0,0 +1,107 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_H_ + +#include + +#include +#include +#include + +namespace flutter_inappwebview_plugin { + +class InAppWebView; +class WebMessageListenerChannelDelegate; + +/** + * Native C++ representation of a WebMessageListener. + * + * Each WebMessageListener has its own MethodChannel for communication + * with the Dart side. This follows the federated plugin pattern where + * callbacks are routed through a dedicated channel (not the main WebView channel). + * + * Channel name pattern: + * com.pichillilorenzo/flutter_inappwebview_web_message_listener_{id}_{jsObjectName} + * + * This matches the iOS/Android architecture where: + * - WebMessageListener is a native class with its own channelDelegate + * - onPostMessage callbacks go directly to Dart via the dedicated channel + * - postMessage (reply) is handled via the dedicated channel + */ +class WebMessageListener { + public: + static constexpr const char* METHOD_CHANNEL_NAME_PREFIX = + "com.pichillilorenzo/flutter_inappwebview_web_message_listener_"; + + /** + * Create a WebMessageListener from a Flutter map value. + * + * Expected map structure: + * - id: String - unique identifier for this listener + * - jsObjectName: String - name of the JS object injected into pages + * - allowedOriginRules: List - origin rules for allowed sources + */ + static std::unique_ptr fromFlValue( + FlBinaryMessenger* messenger, + FlValue* map, + InAppWebView* webView); + + WebMessageListener(FlBinaryMessenger* messenger, + const std::string& id, + const std::string& jsObjectName, + const std::set& allowedOriginRules, + InAppWebView* webView); + ~WebMessageListener(); + + const std::string& id() const { return id_; } + const std::string& jsObjectName() const { return jsObjectName_; } + const std::set& allowedOriginRules() const { return allowedOriginRules_; } + WebMessageListenerChannelDelegate* channelDelegate() const { return channelDelegate_.get(); } + + /** + * Check if the given origin is allowed by the origin rules. + * + * @param scheme URL scheme (e.g., "https") + * @param host Hostname (e.g., "example.com") + * @param port Port number (0 for default) + * @return true if the origin is allowed + */ + bool isOriginAllowed(const std::string& scheme, + const std::string& host, + int port) const; + + /** + * Called when JavaScript posts a message through this listener. + * Routes the callback to Dart via the dedicated channel. + * + * @param messageData The message data (string or array buffer as JSON) + * @param messageType 0 for string, 1 for arrayBuffer + * @param sourceOrigin The origin URL that sent the message + * @param isMainFrame Whether the message came from the main frame + */ + void onPostMessage(const std::string* messageData, + int64_t messageType, + const std::string& sourceOrigin, + bool isMainFrame); + + /** + * Dispose of this WebMessageListener, releasing resources. + */ + void dispose(); + + // Allow channel delegate to access webView_ + friend class WebMessageListenerChannelDelegate; + + // Public accessor for webView (needed by channel delegate) + InAppWebView* webView() const { return webView_; } + + private: + std::string id_; + std::string jsObjectName_; + std::set allowedOriginRules_; + InAppWebView* webView_; // Weak reference - InAppWebView owns WebMessageListeners + std::unique_ptr channelDelegate_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.cc b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.cc new file mode 100644 index 00000000..6d3753ec --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.cc @@ -0,0 +1,195 @@ +#include "web_message_listener_channel_delegate.h" + +#include + +#include "../in_app_webview/in_app_webview.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "web_message_listener.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +WebMessageListenerChannelDelegate::WebMessageListenerChannelDelegate( + WebMessageListener* webMessageListener, + FlBinaryMessenger* messenger, + const std::string& channelName) + : ChannelDelegate(messenger, channelName), + webMessageListener_(webMessageListener) { +} + +WebMessageListenerChannelDelegate::~WebMessageListenerChannelDelegate() { + debugLog("dealloc WebMessageListenerChannelDelegate"); + webMessageListener_ = nullptr; +} + +void WebMessageListenerChannelDelegate::HandleMethodCall(FlMethodCall* method_call) { + if (webMessageListener_ == nullptr) { + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + return; + } + + const gchar* methodName = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(methodName, "postMessage")) { + // Handle reply from Dart to JavaScript + // This is called when JavaScriptReplyProxy.postMessage() is invoked on the Dart side + + InAppWebView* webView = webMessageListener_->webView(); + if (webView == nullptr || webView->webview() == nullptr) { + g_autoptr(FlValue) result = fl_value_new_bool(false); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + FlValue* message_value = get_fl_map_value_raw(args, "message"); + + std::string messageData = ""; + int64_t messageType = 0; // 0 = string, 1 = arrayBuffer + + if (message_value != nullptr && fl_value_get_type(message_value) == FL_VALUE_TYPE_MAP) { + FlValue* data_value = fl_value_lookup_string(message_value, "data"); + FlValue* type_value = fl_value_lookup_string(message_value, "type"); + + if (data_value != nullptr) { + if (fl_value_get_type(data_value) == FL_VALUE_TYPE_STRING) { + messageData = fl_value_get_string(data_value); + } else if (fl_value_get_type(data_value) == FL_VALUE_TYPE_UINT8_LIST) { + // Convert bytes to comma-separated values for JavaScript + const uint8_t* bytes = fl_value_get_uint8_list(data_value); + size_t length = fl_value_get_length(data_value); + for (size_t i = 0; i < length; i++) { + if (i > 0) messageData += ","; + messageData += std::to_string(bytes[i]); + } + } + } + if (type_value != nullptr && fl_value_get_type(type_value) == FL_VALUE_TYPE_INT) { + messageType = fl_value_get_int(type_value); + } + } + + // Build the JavaScript message expression + std::string messageDataJs; + if (messageType == 1) { + // ArrayBuffer - messageData contains comma-separated byte values + messageDataJs = "new Uint8Array([" + messageData + "]).buffer"; + } else { + // String - escape for JavaScript + std::string escaped; + escaped.reserve(messageData.size() * 2); + for (char c : messageData) { + switch (c) { + case '\\': escaped += "\\\\"; break; + case '"': escaped += "\\\""; break; + case '\n': escaped += "\\n"; break; + case '\r': escaped += "\\r"; break; + case '\t': escaped += "\\t"; break; + default: escaped += c; break; + } + } + messageDataJs = "\"" + escaped + "\""; + } + + // Escape the jsObjectName for JavaScript + std::string jsObjectNameEscaped = webMessageListener_->jsObjectName(); + size_t pos = 0; + while ((pos = jsObjectNameEscaped.find("'", pos)) != std::string::npos) { + jsObjectNameEscaped.replace(pos, 1, "\\'"); + pos += 2; + } + + // Build JavaScript to dispatch message to the listener's callbacks + // This matches iOS WebMessageListenerChannelDelegate.postMessage behavior + std::string js = R"JS( +(function() { + var webMessageListener = window[')JS" + jsObjectNameEscaped + R"JS(']; + if (webMessageListener != null) { + var event = {data: )JS" + messageDataJs + R"JS(}; + if (webMessageListener.onmessage != null) { + webMessageListener.onmessage(event); + } + for (var listener of webMessageListener.listeners) { + listener(event); + } + } +})(); +)JS"; + + // Execute the JavaScript + webView->evaluateJavascript(js, std::nullopt, nullptr); + + g_autoptr(FlValue) result = fl_value_new_bool(true); + fl_method_call_respond_success(method_call, result, nullptr); + return; + } + + fl_method_call_respond_not_implemented(method_call, nullptr); +} + +void WebMessageListenerChannelDelegate::onPostMessage(const std::string* messageData, + int64_t messageType, + const std::string* sourceOrigin, + bool isMainFrame) const { + if (channel_ == nullptr) { + return; + } + + // Build message map if there's message data + FlValue* messageMap = nullptr; + if (messageData != nullptr) { + FlValue* dataValue = nullptr; + if (messageType == 1) { + // ArrayBuffer - convert comma-separated values to byte array + std::vector bytes; + std::string value; + for (char c : *messageData) { + if (c == ',') { + if (!value.empty()) { + try { + bytes.push_back(static_cast(std::stoi(value))); + } catch (...) {} + value.clear(); + } + } else { + value += c; + } + } + if (!value.empty()) { + try { + bytes.push_back(static_cast(std::stoi(value))); + } catch (...) {} + } + dataValue = fl_value_new_uint8_list(bytes.data(), bytes.size()); + } else { + dataValue = make_fl_value(*messageData); + } + messageMap = to_fl_map({ + {"data", dataValue}, + {"type", make_fl_value(messageType)}, + }); + } + + g_autoptr(FlValue) args = to_fl_map({ + {"message", messageMap != nullptr ? messageMap : fl_value_new_null()}, + {"sourceOrigin", sourceOrigin != nullptr ? make_fl_value(*sourceOrigin) : fl_value_new_null()}, + {"isMainFrame", make_fl_value(isMainFrame)}, + }); + + invokeMethod("onPostMessage", args); +} + +void WebMessageListenerChannelDelegate::dispose() { + debugLog("WebMessageListenerChannelDelegate::dispose"); + unregisterMethodCallHandler(); + webMessageListener_ = nullptr; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.h b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.h new file mode 100644 index 00000000..53d24039 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_message/web_message_listener_channel_delegate.h @@ -0,0 +1,61 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_CHANNEL_DELEGATE_H_ + +#include + +#include +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class WebMessageListener; + +/** + * Channel delegate for WebMessageListener. + * + * Handles method calls from Dart (like postMessage for replies) + * and invokes callbacks to Dart (like onPostMessage when JS posts a message). + * + * This follows the federated plugin pattern matching iOS/Android: + * - Dedicated MethodChannel per WebMessageListener instance + * - Channel name: com.pichillilorenzo/flutter_inappwebview_web_message_listener_{id}_{jsObjectName} + */ +class WebMessageListenerChannelDelegate : public ChannelDelegate { + public: + WebMessageListenerChannelDelegate(WebMessageListener* webMessageListener, + FlBinaryMessenger* messenger, + const std::string& channelName); + ~WebMessageListenerChannelDelegate() override; + + /** + * Handle method calls from Dart. + * Currently handles: + * - postMessage: Send a reply message from native to JavaScript + */ + void HandleMethodCall(FlMethodCall* method_call) override; + + /** + * Invoke onPostMessage callback on the Dart side. + * Called when JavaScript posts a message through the WebMessageListener. + * + * @param messageData The message data (may be null) + * @param messageType 0 for string, 1 for arrayBuffer + * @param sourceOrigin The origin URL that sent the message (may be null) + * @param isMainFrame Whether the message came from the main frame + */ + void onPostMessage(const std::string* messageData, + int64_t messageType, + const std::string* sourceOrigin, + bool isMainFrame) const; + + void dispose(); + + private: + WebMessageListener* webMessageListener_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_MESSAGE_LISTENER_CHANNEL_DELEGATE_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.cc b/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.cc new file mode 100644 index 00000000..cb737732 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.cc @@ -0,0 +1,373 @@ +#include "web_storage_manager.h" + +#include + +#include "plugin_instance.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +WebStorageManager::WebStorageManager(PluginInstance* plugin) + : plugin_(plugin), channel_(nullptr), data_manager_(nullptr) { + // Get the binary messenger from the plugin + FlBinaryMessenger* messenger = plugin_->messenger(); + + // Create the method channel + channel_ = fl_method_channel_new( + messenger, + "com.pichillilorenzo/flutter_inappwebview_webstoragemanager", + FL_METHOD_CODEC(fl_standard_method_codec_new())); + + // Set the method call handler + fl_method_channel_set_method_call_handler( + channel_, HandleMethodCall, this, nullptr); + + // Get the default website data manager from the network session + WebKitNetworkSession* session = webkit_network_session_get_default(); + if (session != nullptr) { + data_manager_ = webkit_network_session_get_website_data_manager(session); + // data_manager_ is owned by session, don't unref it + } +} + +WebStorageManager::~WebStorageManager() { + if (channel_ != nullptr) { + fl_method_channel_set_method_call_handler(channel_, nullptr, nullptr, nullptr); + g_object_unref(channel_); + channel_ = nullptr; + } + // data_manager_ is owned by the network session, don't unref it + data_manager_ = nullptr; + plugin_ = nullptr; +} + +void WebStorageManager::HandleMethodCall(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + auto* self = static_cast(user_data); + const gchar* method = fl_method_call_get_name(method_call); + + if (string_equals(method, "fetchDataRecords")) { + self->fetchDataRecords(method_call); + } else if (string_equals(method, "removeDataFor")) { + self->removeDataFor(method_call); + } else if (string_equals(method, "removeDataModifiedSince")) { + self->removeDataModifiedSince(method_call); + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +WebKitWebsiteDataTypes WebStorageManager::parseDataTypes(FlValue* dataTypesValue) { + WebKitWebsiteDataTypes types = static_cast(0); + + if (dataTypesValue == nullptr || + fl_value_get_type(dataTypesValue) != FL_VALUE_TYPE_LIST) { + return types; + } + + size_t length = fl_value_get_length(dataTypesValue); + for (size_t i = 0; i < length; i++) { + FlValue* item = fl_value_get_list_value(dataTypesValue, i); + if (fl_value_get_type(item) != FL_VALUE_TYPE_STRING) { + continue; + } + + const char* typeStr = fl_value_get_string(item); + + if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_DISK_CACHE") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_DISK_CACHE); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_MEMORY_CACHE") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_MEMORY_CACHE); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_COOKIES") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_COOKIES); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_SESSION_STORAGE") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_SESSION_STORAGE); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_LOCAL_STORAGE") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_LOCAL_STORAGE); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES); + } else if (strcmp(typeStr, "WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS") == 0) { + types = static_cast(types | WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS); + } + } + + return types; +} + +FlValue* WebStorageManager::dataTypesToFlValue(WebKitWebsiteDataTypes types) { + g_autoptr(FlValue) list = fl_value_new_list(); + + if (types & WEBKIT_WEBSITE_DATA_DISK_CACHE) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_DISK_CACHE")); + } + if (types & WEBKIT_WEBSITE_DATA_MEMORY_CACHE) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_MEMORY_CACHE")); + } + if (types & WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE")); + } + if (types & WEBKIT_WEBSITE_DATA_COOKIES) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_COOKIES")); + } + if (types & WEBKIT_WEBSITE_DATA_SESSION_STORAGE) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_SESSION_STORAGE")); + } + if (types & WEBKIT_WEBSITE_DATA_LOCAL_STORAGE) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_LOCAL_STORAGE")); + } + if (types & WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES")); + } + if (types & WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS) { + fl_value_append_take(list, fl_value_new_string("WEBKIT_WEBSITE_DATA_SERVICE_WORKER_REGISTRATIONS")); + } + + return fl_value_ref(list); +} + +void WebStorageManager::fetchDataRecords(FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + FlValue* dataTypesValue = fl_value_lookup_string(args, "dataTypes"); + + WebKitWebsiteDataTypes types = parseDataTypes(dataTypesValue); + + // Store method_call for async callback + g_object_ref(method_call); + + webkit_website_data_manager_fetch( + data_manager_, + types, + nullptr, // GCancellable + [](GObject* source_object, GAsyncResult* res, gpointer user_data) { + auto* method_call = static_cast(user_data); + auto* data_manager = WEBKIT_WEBSITE_DATA_MANAGER(source_object); + + GError* error = nullptr; + GList* records = webkit_website_data_manager_fetch_finish( + data_manager, res, &error); + + if (error != nullptr) { + fl_method_call_respond_error( + method_call, + "FETCH_ERROR", + error->message, + nullptr, + nullptr); + g_error_free(error); + g_object_unref(method_call); + return; + } + + g_autoptr(FlValue) result = fl_value_new_list(); + + for (GList* l = records; l != nullptr; l = l->next) { + WebKitWebsiteData* data = static_cast(l->data); + const char* name = webkit_website_data_get_name(data); + WebKitWebsiteDataTypes dataTypes = webkit_website_data_get_types(data); + + g_autoptr(FlValue) record = (name != nullptr) + ? to_fl_map({ + {"displayName", make_fl_value(name)}, + {"dataTypes", WebStorageManager::dataTypesToFlValue(dataTypes)}, + }) + : to_fl_map({ + {"dataTypes", WebStorageManager::dataTypesToFlValue(dataTypes)}, + }); + + fl_value_append(result, record); + } + + g_list_free_full(records, reinterpret_cast(webkit_website_data_unref)); + + fl_method_call_respond_success(method_call, result, nullptr); + g_object_unref(method_call); + }, + method_call); +} + +void WebStorageManager::removeDataFor(FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + FlValue* dataTypesValue = fl_value_lookup_string(args, "dataTypes"); + FlValue* recordListValue = fl_value_lookup_string(args, "recordList"); + + WebKitWebsiteDataTypes types = parseDataTypes(dataTypesValue); + + if (recordListValue == nullptr || + fl_value_get_type(recordListValue) != FL_VALUE_TYPE_LIST || + fl_value_get_length(recordListValue) == 0) { + // No records to delete + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + return; + } + + // Extract display names from the record list + std::vector displayNames; + size_t length = fl_value_get_length(recordListValue); + for (size_t i = 0; i < length; i++) { + FlValue* record = fl_value_get_list_value(recordListValue, i); + FlValue* displayNameValue = fl_value_lookup_string(record, "displayName"); + if (displayNameValue != nullptr && + fl_value_get_type(displayNameValue) == FL_VALUE_TYPE_STRING) { + displayNames.push_back(fl_value_get_string(displayNameValue)); + } + } + + if (displayNames.empty()) { + fl_method_call_respond_success(method_call, fl_value_new_bool(TRUE), nullptr); + return; + } + + // We need to first fetch records to get WebKitWebsiteData pointers, + // then remove matching ones + struct RemoveContext { + FlMethodCall* method_call; + WebKitWebsiteDataManager* data_manager; + WebKitWebsiteDataTypes types; + std::vector displayNames; + }; + + auto* ctx = new RemoveContext{method_call, data_manager_, types, std::move(displayNames)}; + g_object_ref(method_call); + + webkit_website_data_manager_fetch( + data_manager_, + types, + nullptr, + [](GObject* source_object, GAsyncResult* res, gpointer user_data) { + auto* ctx = static_cast(user_data); + auto* data_manager = WEBKIT_WEBSITE_DATA_MANAGER(source_object); + + GError* error = nullptr; + GList* all_records = webkit_website_data_manager_fetch_finish( + data_manager, res, &error); + + if (error != nullptr) { + fl_method_call_respond_error( + ctx->method_call, "FETCH_ERROR", error->message, nullptr, nullptr); + g_error_free(error); + g_object_unref(ctx->method_call); + delete ctx; + return; + } + + // Find matching records + GList* matching_records = nullptr; + for (GList* l = all_records; l != nullptr; l = l->next) { + WebKitWebsiteData* data = static_cast(l->data); + const char* name = webkit_website_data_get_name(data); + if (name != nullptr) { + for (const auto& displayName : ctx->displayNames) { + if (displayName == name) { + matching_records = g_list_prepend(matching_records, + webkit_website_data_ref(data)); + break; + } + } + } + } + + g_list_free_full(all_records, + reinterpret_cast(webkit_website_data_unref)); + + if (matching_records == nullptr) { + fl_method_call_respond_success(ctx->method_call, + fl_value_new_bool(TRUE), nullptr); + g_object_unref(ctx->method_call); + delete ctx; + return; + } + + // Now remove the matching records + webkit_website_data_manager_remove( + ctx->data_manager, + ctx->types, + matching_records, + nullptr, + [](GObject* source_object, GAsyncResult* res, gpointer user_data) { + auto* ctx = static_cast(user_data); + auto* data_manager = WEBKIT_WEBSITE_DATA_MANAGER(source_object); + + GError* error = nullptr; + gboolean success = webkit_website_data_manager_remove_finish( + data_manager, res, &error); + + if (error != nullptr) { + fl_method_call_respond_error( + ctx->method_call, "REMOVE_ERROR", error->message, nullptr, nullptr); + g_error_free(error); + } else { + fl_method_call_respond_success(ctx->method_call, + fl_value_new_bool(success), nullptr); + } + + g_object_unref(ctx->method_call); + delete ctx; + }, + ctx); + + g_list_free_full(matching_records, + reinterpret_cast(webkit_website_data_unref)); + }, + ctx); +} + +void WebStorageManager::removeDataModifiedSince(FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + FlValue* dataTypesValue = fl_value_lookup_string(args, "dataTypes"); + FlValue* timestampValue = fl_value_lookup_string(args, "timestamp"); + + WebKitWebsiteDataTypes types = parseDataTypes(dataTypesValue); + + // Get timestamp (seconds since epoch) + gint64 timestamp = 0; + if (timestampValue != nullptr && + fl_value_get_type(timestampValue) == FL_VALUE_TYPE_INT) { + timestamp = fl_value_get_int(timestampValue); + } + + // Convert to GDateTime + GDateTime* datetime = g_date_time_new_from_unix_utc(timestamp); + GTimeSpan timespan = g_date_time_to_unix(datetime); + g_date_time_unref(datetime); + + g_object_ref(method_call); + + webkit_website_data_manager_clear( + data_manager_, + types, + timespan, + nullptr, // GCancellable + [](GObject* source_object, GAsyncResult* res, gpointer user_data) { + auto* method_call = static_cast(user_data); + auto* data_manager = WEBKIT_WEBSITE_DATA_MANAGER(source_object); + + GError* error = nullptr; + gboolean success = webkit_website_data_manager_clear_finish( + data_manager, res, &error); + + if (error != nullptr) { + fl_method_call_respond_error( + method_call, "CLEAR_ERROR", error->message, nullptr, nullptr); + g_error_free(error); + } else { + fl_method_call_respond_success(method_call, + fl_value_new_bool(success), nullptr); + } + + g_object_unref(method_call); + }, + method_call); +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.h b/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.h new file mode 100644 index 00000000..6cd7e2f9 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/web_storage_manager.h @@ -0,0 +1,62 @@ +#ifndef FLUTTER_INAPPWEBVIEW_LINUX_WEB_STORAGE_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_LINUX_WEB_STORAGE_MANAGER_H_ + +#include +#include + +#include +#include + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/// WebStorageManager handles website data management using WPE WebKit's +/// WebKitWebsiteDataManager API. +class WebStorageManager { + public: + /// Creates a new WebStorageManager. + /// @param plugin The plugin instance for accessing messenger. + explicit WebStorageManager(PluginInstance* plugin); + ~WebStorageManager(); + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + // Prevent copying + WebStorageManager(const WebStorageManager&) = delete; + WebStorageManager& operator=(const WebStorageManager&) = delete; + + private: + PluginInstance* plugin_ = nullptr; + + /// Handle method calls from Flutter. + static void HandleMethodCall(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data); + + /// Fetch website data records. + void fetchDataRecords(FlMethodCall* method_call); + + /// Remove data for specific records. + void removeDataFor(FlMethodCall* method_call); + + /// Remove data modified since a specific date. + void removeDataModifiedSince(FlMethodCall* method_call); + + /// Convert string list to WebKitWebsiteDataTypes bitmask. + static WebKitWebsiteDataTypes parseDataTypes(FlValue* dataTypesValue); + + /// Convert WebKitWebsiteDataTypes bitmask to string list. + static FlValue* dataTypesToFlValue(WebKitWebsiteDataTypes types); + + /// The Flutter method channel. + FlMethodChannel* channel_; + + /// The website data manager. + WebKitWebsiteDataManager* data_manager_; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_LINUX_WEB_STORAGE_MANAGER_H_ diff --git a/.vendor/flutter_inappwebview_linux/linux/webview_environment.cc b/.vendor/flutter_inappwebview_linux/linux/webview_environment.cc new file mode 100644 index 00000000..23401395 --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/webview_environment.cc @@ -0,0 +1,281 @@ +#include "webview_environment.h" + +#include + +#include +#include + +#include "plugin_instance.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin { + +namespace { +// Helper to compare method names +bool string_equals(const gchar* a, const char* b) { + return strcmp(a, b) == 0; +} +} // namespace + +// ============================================================================ +// WebViewEnvironmentInstanceChannelDelegate Implementation +// ============================================================================ + +WebViewEnvironmentInstanceChannelDelegate::WebViewEnvironmentInstanceChannelDelegate( + FlBinaryMessenger* messenger, + const std::string& id, + std::function disposeCallback) + : ChannelDelegate(messenger, + "com.pichillilorenzo/flutter_webview_environment_" + id), + id_(id), + disposeCallback_(std::move(disposeCallback)) {} + +WebViewEnvironmentInstanceChannelDelegate::~WebViewEnvironmentInstanceChannelDelegate() { + debugLog("dealloc WebViewEnvironmentInstanceChannelDelegate id=" + id_); + if (context_ != nullptr) { + g_object_unref(context_); + context_ = nullptr; + } +} + +void WebViewEnvironmentInstanceChannelDelegate::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + + if (string_equals(method, "dispose")) { + // Dispose this instance - call the callback to remove from parent's map + disposeCallback_(id_); + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + } else if (string_equals(method, "isSpellCheckingEnabled")) { + bool enabled = isSpellCheckingEnabled(); + fl_method_call_respond_success(method_call, fl_value_new_bool(enabled), nullptr); + } else if (string_equals(method, "getSpellCheckingLanguages")) { + auto languages = getSpellCheckingLanguages(); + FlValue* list = fl_value_new_list(); + for (const auto& lang : languages) { + fl_value_append_take(list, fl_value_new_string(lang.c_str())); + } + fl_method_call_respond_success(method_call, list, nullptr); + } else if (string_equals(method, "getCacheModel")) { + int model = getCacheModel(); + fl_method_call_respond_success(method_call, fl_value_new_int(model), nullptr); + } else if (string_equals(method, "isAutomationAllowed")) { + bool allowed = isAutomationAllowed(); + fl_method_call_respond_success(method_call, fl_value_new_bool(allowed), nullptr); + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +bool WebViewEnvironmentInstanceChannelDelegate::isSpellCheckingEnabled() const { + if (context_ == nullptr) { + return false; + } + return webkit_web_context_get_spell_checking_enabled(context_); +} + +std::vector WebViewEnvironmentInstanceChannelDelegate::getSpellCheckingLanguages() const { + std::vector result; + if (context_ == nullptr) { + return result; + } + const gchar* const* languages = webkit_web_context_get_spell_checking_languages(context_); + if (languages != nullptr) { + for (int i = 0; languages[i] != nullptr; i++) { + result.push_back(std::string(languages[i])); + } + } + return result; +} + +int WebViewEnvironmentInstanceChannelDelegate::getCacheModel() const { + if (context_ == nullptr) { + return 1; // Default: WEBKIT_CACHE_MODEL_WEB_BROWSER + } + return static_cast(webkit_web_context_get_cache_model(context_)); +} + +bool WebViewEnvironmentInstanceChannelDelegate::isAutomationAllowed() const { + if (context_ == nullptr) { + return false; + } + return webkit_web_context_is_automation_allowed(context_); +} + +// ============================================================================ +// WebViewEnvironment Implementation +// ============================================================================ + +WebKitWebContext* WebViewEnvironment::getWebContext(const std::string& id) const { + if (id.empty()) { + return nullptr; + } + auto* delegate = getInstance(id); + if (delegate == nullptr) { + return nullptr; + } + return delegate->context(); +} + +WebViewEnvironment::WebViewEnvironment(PluginInstance* plugin) + : ChannelDelegate(plugin->messenger(), METHOD_CHANNEL_NAME), + plugin_(plugin), + messenger_(plugin->messenger()) {} + +WebViewEnvironment::~WebViewEnvironment() { + debugLog("dealloc WebViewEnvironment"); + // Clean up all instances + instances_.clear(); + plugin_ = nullptr; +} + +void WebViewEnvironment::HandleMethodCall(FlMethodCall* method_call) { + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (string_equals(method, "getAvailableVersion")) { + std::string version = getAvailableVersion(); + fl_method_call_respond_success(method_call, fl_value_new_string(version.c_str()), nullptr); + } else if (string_equals(method, "create")) { + auto id = get_fl_map_value(args, "id", ""); + if (id.empty()) { + fl_method_call_respond_error(method_call, "INVALID_ARGUMENT", "Missing 'id' argument", + nullptr, nullptr); + return; + } + FlValue* settings = fl_value_lookup_string(args, "settings"); + create(id, settings); + fl_method_call_respond_success(method_call, fl_value_new_null(), nullptr); + } else { + fl_method_call_respond_not_implemented(method_call, nullptr); + } +} + +std::string WebViewEnvironment::getAvailableVersion() { + // Get the WPE WebKit version using the webkit version functions + guint major = webkit_get_major_version(); + guint minor = webkit_get_minor_version(); + guint micro = webkit_get_micro_version(); + + std::ostringstream version; + version << major << "." << minor << "." << micro; + return version.str(); +} + +void WebViewEnvironment::create(const std::string& id, FlValue* settings) { + debugLog("WebViewEnvironment::create id=" + id); + + // Check if we need to use a specific timezone override + // This must be set at construction time via the time-zone-override property + auto timeZoneOverride = get_optional_fl_map_value(settings, "timeZoneOverride"); + + WebKitWebContext* context = nullptr; + if (timeZoneOverride.has_value() && !timeZoneOverride->empty()) { + // Create WebContext with timezone override property + context = WEBKIT_WEB_CONTEXT(g_object_new(WEBKIT_TYPE_WEB_CONTEXT, + "time-zone-override", timeZoneOverride->c_str(), + nullptr)); + } else { + // Create a default WebKitWebContext + context = webkit_web_context_new(); + } + + if (context == nullptr) { + errorLog("WebViewEnvironment::create - Failed to create WebKitWebContext"); + return; + } + + // Apply settings from creation params + if (settings != nullptr && fl_value_get_type(settings) == FL_VALUE_TYPE_MAP) { + // cacheModel + auto cacheModel = get_optional_fl_map_value(settings, "cacheModel"); + if (cacheModel.has_value()) { + webkit_web_context_set_cache_model(context, + static_cast(cacheModel.value())); + } + + // spellCheckingEnabled + auto spellCheckingEnabled = get_optional_fl_map_value(settings, "spellCheckingEnabled"); + if (spellCheckingEnabled.has_value()) { + webkit_web_context_set_spell_checking_enabled(context, spellCheckingEnabled.value()); + } + + // spellCheckingLanguages + auto spellCheckingLanguages = get_optional_fl_map_value>( + settings, "spellCheckingLanguages"); + if (spellCheckingLanguages.has_value() && !spellCheckingLanguages->empty()) { + // Convert to null-terminated array of C strings + std::vector langs; + for (const auto& lang : *spellCheckingLanguages) { + langs.push_back(lang.c_str()); + } + langs.push_back(nullptr); // Null terminate + webkit_web_context_set_spell_checking_languages(context, langs.data()); + } + + // preferredLanguages + auto preferredLanguages = get_optional_fl_map_value>( + settings, "preferredLanguages"); + if (preferredLanguages.has_value() && !preferredLanguages->empty()) { + std::vector langs; + for (const auto& lang : *preferredLanguages) { + langs.push_back(lang.c_str()); + } + langs.push_back(nullptr); + webkit_web_context_set_preferred_languages(context, langs.data()); + } + + // automationAllowed + auto automationAllowed = get_optional_fl_map_value(settings, "automationAllowed"); + if (automationAllowed.has_value()) { + webkit_web_context_set_automation_allowed(context, automationAllowed.value()); + } + + // webProcessExtensionsDirectory + auto extensionsDir = get_optional_fl_map_value( + settings, "webProcessExtensionsDirectory"); + if (extensionsDir.has_value() && !extensionsDir->empty()) { + webkit_web_context_set_web_process_extensions_directory(context, extensionsDir->c_str()); + } + + // sandboxPaths + auto sandboxPaths = get_optional_fl_map_value>( + settings, "sandboxPaths"); + if (sandboxPaths.has_value()) { + for (const auto& path : *sandboxPaths) { + if (!path.empty()) { + webkit_web_context_add_path_to_sandbox(context, path.c_str(), FALSE); + } + } + } + } + + // Create instance channel delegate + auto instanceDelegate = std::make_unique( + messenger_, id, + [this](const std::string& envId) { disposeEnvironment(envId); }); + instanceDelegate->setContext(context); + + // Store in map + instances_[id] = std::move(instanceDelegate); +} + +void WebViewEnvironment::disposeEnvironment(const std::string& id) { + debugLog("WebViewEnvironment::disposeEnvironment id=" + id); + + auto it = instances_.find(id); + if (it != instances_.end()) { + // Remove from map - this will trigger destructor which cleans up context + instances_.erase(it); + } +} + +WebViewEnvironmentInstanceChannelDelegate* WebViewEnvironment::getInstance(const std::string& id) const { + auto it = instances_.find(id); + if (it != instances_.end()) { + return it->second.get(); + } + return nullptr; +} + +} // namespace flutter_inappwebview_plugin diff --git a/.vendor/flutter_inappwebview_linux/linux/webview_environment.h b/.vendor/flutter_inappwebview_linux/linux/webview_environment.h new file mode 100644 index 00000000..0e44ec6e --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/linux/webview_environment.h @@ -0,0 +1,101 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ + +#include +#include + +#include +#include +#include +#include + +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin { + +class PluginInstance; + +/** + * Instance channel delegate for individual WebViewEnvironment instances. + * Handles instance-specific method calls like dispose, isSpellCheckingEnabled, etc. + */ +class WebViewEnvironmentInstanceChannelDelegate : public ChannelDelegate { + public: + WebViewEnvironmentInstanceChannelDelegate(FlBinaryMessenger* messenger, + const std::string& id, + std::function disposeCallback); + ~WebViewEnvironmentInstanceChannelDelegate() override; + + void HandleMethodCall(FlMethodCall* method_call) override; + + WebKitWebContext* context() const { return context_; } + void setContext(WebKitWebContext* context) { context_ = context; } + + // Getter methods for WebContext properties + bool isSpellCheckingEnabled() const; + std::vector getSpellCheckingLanguages() const; + int getCacheModel() const; + bool isAutomationAllowed() const; + + private: + std::string id_; + WebKitWebContext* context_ = nullptr; + std::function disposeCallback_; +}; + +/** + * Manages WebView Environment operations for WPE WebKit. + * Provides access to the WPE WebKit version information and + * WebKitWebContext instance management. + */ +class WebViewEnvironment : public ChannelDelegate { + public: + static constexpr const char* METHOD_CHANNEL_NAME = + "com.pichillilorenzo/flutter_webview_environment"; + + WebViewEnvironment(PluginInstance* plugin); + ~WebViewEnvironment() override; + + /// Get the plugin instance + PluginInstance* plugin() const { return plugin_; } + + void HandleMethodCall(FlMethodCall* method_call) override; + + /** + * Get the WPE WebKit version string (e.g., "2.42.0"). + */ + static std::string getAvailableVersion(); + + /** + * Get the WebKitWebContext for the given environment ID. + * Returns nullptr if not found. + */ + WebKitWebContext* getWebContext(const std::string& id) const; + + private: + PluginInstance* plugin_ = nullptr; + FlBinaryMessenger* messenger_; + + // Map of environment ID -> instance channel delegate + std::map> instances_; + + /** + * Create a new WebViewEnvironment instance with the given ID and settings. + */ + void create(const std::string& id, FlValue* settings); + + /** + * Dispose an environment instance by ID. + */ + void disposeEnvironment(const std::string& id); + + /** + * Get a WebViewEnvironment instance by ID. + * Returns nullptr if not found. + */ + WebViewEnvironmentInstanceChannelDelegate* getInstance(const std::string& id) const; +}; + +} // namespace flutter_inappwebview_plugin + +#endif // FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ diff --git a/.vendor/flutter_inappwebview_linux/pubspec.yaml b/.vendor/flutter_inappwebview_linux/pubspec.yaml new file mode 100644 index 00000000..2a96f65c --- /dev/null +++ b/.vendor/flutter_inappwebview_linux/pubspec.yaml @@ -0,0 +1,82 @@ +name: flutter_inappwebview_linux +description: "Linux implementation of flutter_inappwebview plugin using WPE WebKit." +version: 0.1.0-beta.1 +homepage: https://inappwebview.dev/ +repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_linux +issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ +topics: + - html + - webview + - webview-flutter + - inappwebview + - browser + +environment: + sdk: ^3.8.0 + flutter: ">=3.32.0" + +dependencies: + flutter: + sdk: flutter + flutter_inappwebview_platform_interface: ^1.4.0-beta.3 + # path: ../flutter_inappwebview_platform_interface + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + implements: flutter_inappwebview + platforms: + linux: + pluginClass: FlutterInappwebviewLinuxPlugin + dartPluginClass: LinuxInAppWebViewPlatform + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/.vendor/flutter_progress_dialog/.gitignore b/.vendor/flutter_progress_dialog/.gitignore new file mode 100644 index 00000000..1c132246 --- /dev/null +++ b/.vendor/flutter_progress_dialog/.gitignore @@ -0,0 +1,35 @@ +.DS_Store +.atom/ +.idea/ +.vscode/ + +.packages +.pub/ +.dart_tool/ +pubspec.lock + +Podfile +Podfile.lock +Pods/ +.symlinks/ +**/Flutter/App.framework/ +**/Flutter/Flutter.framework/ +**/Flutter/Generated.xcconfig +**/Flutter/flutter_assets/ +ServiceDefinitions.json +xcuserdata/ + +local.properties +.gradle/ +gradlew +gradlew.bat +gradle-wrapper.jar +*.iml + +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m +GeneratedPluginRegistrant.java +build/ +.flutter-plugins +/example/android/keystore.properties + diff --git a/.vendor/flutter_progress_dialog/.metadata b/.vendor/flutter_progress_dialog/.metadata new file mode 100644 index 00000000..dadd408e --- /dev/null +++ b/.vendor/flutter_progress_dialog/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b712a172f9694745f50505c93340883493b505e5 + channel: stable + +project_type: plugin diff --git a/.vendor/flutter_progress_dialog/CHANGELOG.md b/.vendor/flutter_progress_dialog/CHANGELOG.md new file mode 100644 index 00000000..4638405e --- /dev/null +++ b/.vendor/flutter_progress_dialog/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial project. diff --git a/.vendor/flutter_progress_dialog/LICENSE b/.vendor/flutter_progress_dialog/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/.vendor/flutter_progress_dialog/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.vendor/flutter_progress_dialog/README.md b/.vendor/flutter_progress_dialog/README.md new file mode 100644 index 00000000..b0e550e0 --- /dev/null +++ b/.vendor/flutter_progress_dialog/README.md @@ -0,0 +1,94 @@ +# Flutter Progress Dialog + +[[pub packages]](https://pub.dartlang.org/packages/flutter_progress_dialog) +| [中文说明](./README_zh-cn.md) + +Flutter progress dialog. Support both Android and iOS platform. + +The progress dialog just display one at the same time. + +The usage inspired by [OpenFlutter/flutter_oktoast](https://github.com/OpenFlutter/flutter_oktoast) + +![Example][1] + +## Usage + +#### 1\. Depend + +Add this to you package's `pubspec.yaml` file: + +```yaml +dependencies: + flutter_progress_dialog: ^0.1.0 +``` + +#### 2\. Install + +Run command: + +```bash +$ flutter packages get +``` + +#### 3\. Import + +Import in Dart code: + +```dart +import 'package:flutter_progress_dialog/flutter_progress_dialog.dart'; +``` + +#### 4\. Display Progress Dialog + +Support two ways to display a progress dialog. + +##### Wrap app widget + +1) Wrap your app widget + +```dart +ProgressDialog( + child: MaterialApp(), +); +``` + +2) Exec `showProgressDialog()` and `dismissProgressDialog()` without parameters. + +```dart +showProgressDialog(); +//dismissProgressDialog(); +``` + +##### Exec showProgressDialog() directly + +You can exec `showProgressDialog()` without wrap app widget, should specify the param: `context: BuildContext`. + +```dart +var dialog = showProgressDialog(context: context); +//dismissProgressDialog(); +``` + +#### 5\. Properties + +ProgressDialog have default style, and you also can custom style or other behavior. + +|Name |Type |Desc | +|:--------------|:-------------------|:------------------------------------------| +|loading |Widget |If specified, default widget will not show | +|loadingText |String |Hint text, just for default widget | +|textStyle |TextStyle |Hint text's style, just for default widget | +|backgroundColor|Color |Background color of the progress dialog | +|radius |double |Radius of the progress dialog | +|onDismiss |Function |Callback for dismissed | +|textDirection |TextDirection |Loading hint text's direction | +|orientation |ProgressOrientation |The direction of spin kit and hint text | + +## Example + +[Example sources](https://github.com/wuzhendev/flutter_progress_dialog/tree/master/example) + +[Example APK](https://raw.githubusercontent.com/wuzhendev/assets/master/flutter_progress_dialog/flutter_progress_dialog_v0.1.0.apk) + +![Example APK Download](https://github.com/wuzhendev/assets/blob/master/flutter_progress_dialog/flutter_progress_dialog_v0.1.0.png?raw=true) + +[1]:https://github.com/wuzhendev/assets/blob/master/flutter_progress_dialog/flutter_progress_dialog_1.jpg?raw=true diff --git a/.vendor/flutter_progress_dialog/README_zh-cn.md b/.vendor/flutter_progress_dialog/README_zh-cn.md new file mode 100644 index 00000000..055e1747 --- /dev/null +++ b/.vendor/flutter_progress_dialog/README_zh-cn.md @@ -0,0 +1,95 @@ +# Flutter Progress Dialog + +[[pub packages]](https://pub.dartlang.org/packages/flutter_progress_dialog) + +Flutter 的加载提示对话框,支持 Android 和 iOS 平台。 + +***同一时间只会显示一个 ProgressDialog。*** + +实现方案参考了项目:[OpenFlutter/flutter_oktoast](https://github.com/OpenFlutter/flutter_oktoast) + +![Example][1] + +## 用法 + +#### 1\. 依赖库 + +在项目的 `pubspec.yaml` 文件中添加依赖: + +```yaml +dependencies: + flutter_progress_dialog: ^0.1.0 +``` + +#### 2\. 获取包 + +执行命令: + +```bash +$ flutter packages get +``` + +#### 3\. 导入库文件 + +导入 flutter_progress_dialog + +```dart +import 'package:flutter_progress_dialog/flutter_progress_dialog.dart'; +``` + +#### 4\. 显示 ProgressDialog + +支持两种方式显示加载中的对话框。 + +##### Wrap app widget + +1) 在 MaterialApp 外层添加 ProgressDialog + +```dart +ProgressDialog( + child: MaterialApp(), +); +``` + +2) 可以在任何页面执行 `showProgressDialog()、dismissProgressDialog()` 方法,不需要传递任何参数。 + +```dart +showProgressDialog(); +//dismissProgressDialog(); +``` + +##### 直接执行 showProgressDialog() 方法 + +可以直接执行 `showProgressDialog()` 方法,此时需要指定参数 `context: BuildContext`。 + +```dart +var dialog = showProgressDialog(context: context); +//dismissProgressDialog(); +``` + +#### 5\. 参数 + +ProgressDialog 有默认的样式,同时还可以根据需求自定义样式,或者指定自定义的加载布局。 + +示例代码中使用 [flutter_spinkit](https://github.com/jogboms/flutter_spinkit) 显示自定义的加载布局,可以进行参考。 + +|Name |Type |Desc | +|:--------------|:-------------------|:-----------------------------------| +|loading |Widget |如果指定了布局,不再显示默认的布局 | +|loadingText |String |提示的文字,只有在显示默认布局时生效 | +|textStyle |TextStyle |提示文字的样式,只有在显示默认布局时生效 | +|backgroundColor|Color |对话框的背景色 | +|radius |double |对话框背景的圆角值 | +|onDismiss |Function |对话框消失时的回调 | +|textDirection |TextDirection |提示文字的排列方向 | +|orientation |ProgressOrientation |加载图标和文字的排列方向(从左到右/从上到下) | + +## 示例 + +[示例代码](https://github.com/wuzhendev/flutter_progress_dialog/tree/master/example) + +[示例APK](https://raw.githubusercontent.com/wuzhendev/assets/master/flutter_progress_dialog/flutter_progress_dialog_v0.1.0.apk) + +![Example APK Download](https://github.com/wuzhendev/assets/blob/master/flutter_progress_dialog/flutter_progress_dialog_v0.1.0.png?raw=true) + +[1]:https://github.com/wuzhendev/assets/blob/master/flutter_progress_dialog/flutter_progress_dialog_1.jpg?raw=true diff --git a/.vendor/flutter_progress_dialog/android/.gitignore b/.vendor/flutter_progress_dialog/android/.gitignore new file mode 100644 index 00000000..c6cbe562 --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/.vendor/flutter_progress_dialog/android/build.gradle b/.vendor/flutter_progress_dialog/android/build.gradle new file mode 100644 index 00000000..cd2bc3f4 --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/build.gradle @@ -0,0 +1,44 @@ +group 'com.flutter.flutter_progress_dialog' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.2.71' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/.vendor/flutter_progress_dialog/android/gradle.properties b/.vendor/flutter_progress_dialog/android/gradle.properties new file mode 100644 index 00000000..2bd6f4fd --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M + diff --git a/.vendor/flutter_progress_dialog/android/settings.gradle b/.vendor/flutter_progress_dialog/android/settings.gradle new file mode 100644 index 00000000..2b4aca5d --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_progress_dialog' diff --git a/.vendor/flutter_progress_dialog/android/src/main/AndroidManifest.xml b/.vendor/flutter_progress_dialog/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3731035a --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/.vendor/flutter_progress_dialog/android/src/main/kotlin/com/flutter/flutter_progress_dialog/FlutterProgressDialogPlugin.kt b/.vendor/flutter_progress_dialog/android/src/main/kotlin/com/flutter/flutter_progress_dialog/FlutterProgressDialogPlugin.kt new file mode 100644 index 00000000..b941e53c --- /dev/null +++ b/.vendor/flutter_progress_dialog/android/src/main/kotlin/com/flutter/flutter_progress_dialog/FlutterProgressDialogPlugin.kt @@ -0,0 +1,25 @@ +package com.flutter.flutter_progress_dialog + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar + +class FlutterProgressDialogPlugin: MethodCallHandler { + companion object { + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "flutter_progress_dialog") + channel.setMethodCallHandler(FlutterProgressDialogPlugin()) + } + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } +} diff --git a/.vendor/flutter_progress_dialog/ios/.gitignore b/.vendor/flutter_progress_dialog/ios/.gitignore new file mode 100644 index 00000000..710ec6cf --- /dev/null +++ b/.vendor/flutter_progress_dialog/ios/.gitignore @@ -0,0 +1,36 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig diff --git a/.vendor/flutter_progress_dialog/ios/Assets/.gitkeep b/.vendor/flutter_progress_dialog/ios/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.h b/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.h new file mode 100644 index 00000000..b32f70f1 --- /dev/null +++ b/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterProgressDialogPlugin : NSObject +@end diff --git a/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.m b/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.m new file mode 100644 index 00000000..5581d429 --- /dev/null +++ b/.vendor/flutter_progress_dialog/ios/Classes/FlutterProgressDialogPlugin.m @@ -0,0 +1,8 @@ +#import "FlutterProgressDialogPlugin.h" +#import + +@implementation FlutterProgressDialogPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterProgressDialogPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/.vendor/flutter_progress_dialog/ios/Classes/SwiftFlutterProgressDialogPlugin.swift b/.vendor/flutter_progress_dialog/ios/Classes/SwiftFlutterProgressDialogPlugin.swift new file mode 100644 index 00000000..e2eff9ac --- /dev/null +++ b/.vendor/flutter_progress_dialog/ios/Classes/SwiftFlutterProgressDialogPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftFlutterProgressDialogPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "flutter_progress_dialog", binaryMessenger: registrar.messenger()) + let instance = SwiftFlutterProgressDialogPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/.vendor/flutter_progress_dialog/ios/flutter_progress_dialog.podspec b/.vendor/flutter_progress_dialog/ios/flutter_progress_dialog.podspec new file mode 100644 index 00000000..7730c2d4 --- /dev/null +++ b/.vendor/flutter_progress_dialog/ios/flutter_progress_dialog.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'flutter_progress_dialog' + s.version = '0.0.1' + s.summary = 'Flutter progress dialog' + s.description = <<-DESC +Flutter progress dialog + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end + diff --git a/.vendor/flutter_progress_dialog/lib/flutter_progress_dialog.dart b/.vendor/flutter_progress_dialog/lib/flutter_progress_dialog.dart new file mode 100644 index 00000000..0515cc9a --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/flutter_progress_dialog.dart @@ -0,0 +1,11 @@ +library flutter_progress_dialog; + +export 'src/progress_dialog.dart' + show + ProgressOrientation, + ProgressVisibleObserver, + showProgressDialog, + showProgressDialogWidget, + dismissProgressDialog, + ProgressDialog, + ProgressFuture; diff --git a/.vendor/flutter_progress_dialog/lib/src/core/future.dart b/.vendor/flutter_progress_dialog/lib/src/core/future.dart new file mode 100644 index 00000000..646ffa5d --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/core/future.dart @@ -0,0 +1,33 @@ +part of '../progress_dialog.dart'; + +/// use the [dismiss] to dismiss ProgressDialog. +class ProgressFuture { + final OverlayEntry _entry; + final VoidCallback? _onDismiss; + bool _isShow = true; + final GlobalKey<_ProgressContainerState> _containerKey; + + ProgressFuture._( + this._entry, + this._onDismiss, + this._containerKey, + ); + + void dismiss({bool showAnim = true}) { + if (!_isShow) { + return; + } + _isShow = false; + _onDismiss?.call(); + ProgressManager().removeFuture(this); + + if (showAnim) { + _containerKey.currentState!.showDismissAnim(); + Future.delayed(_opacityDuration, () { + _entry.remove(); + }); + } else { + _entry.remove(); + } + } +} diff --git a/.vendor/flutter_progress_dialog/lib/src/core/manager.dart b/.vendor/flutter_progress_dialog/lib/src/core/manager.dart new file mode 100644 index 00000000..0dfe5b4e --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/core/manager.dart @@ -0,0 +1,28 @@ +import '../progress_dialog.dart'; + +class ProgressManager { + ProgressManager._(); + + static ProgressManager? _instance; + + factory ProgressManager() { + _instance ??= ProgressManager._(); + return _instance!; + } + + Set futureSet = Set(); + + void dismissAll({bool showAnim = false}) { + futureSet.toList().forEach((v) { + v.dismiss(showAnim: showAnim); + }); + } + + void removeFuture(ProgressFuture future) { + futureSet.remove(future); + } + + void addFuture(ProgressFuture future) { + futureSet.add(future); + } +} diff --git a/.vendor/flutter_progress_dialog/lib/src/progress_dialog.dart b/.vendor/flutter_progress_dialog/lib/src/progress_dialog.dart new file mode 100644 index 00000000..1b873bcd --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/progress_dialog.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; + +import 'core/manager.dart'; + +part 'widget/container.dart'; + +part 'widget/progress.dart'; + +part 'widget/theme.dart'; + +part 'core/future.dart'; + +enum ProgressOrientation { horizontal, vertical } + +LinkedHashMap<_ProgressDialogState, BuildContext> _contextMap = LinkedHashMap(); + +const _opacityDuration = Duration(milliseconds: 250); +const _defaultLoadingText = "请稍候"; + +/// show progress dialog with [msg], +ProgressFuture showProgressDialog({ + BuildContext? context, + Widget? loading, + String? loadingText, + TextStyle? textStyle, + Color? backgroundColor, + double? radius, + VoidCallback? onDismiss, + TextDirection? textDirection, + ProgressOrientation? orientation, +}) { + context ??= _contextMap.values.first; + _ProgressTheme? theme = _ProgressTheme.of(context); + theme ??= _ProgressTheme.origin; + textStyle ??= Theme.of(context).textTheme.bodyLarge!.copyWith(fontSize: 16.0); + backgroundColor ??= theme.backgroundColor; + radius ??= theme.radius; + textDirection ??= theme.textDirection; + orientation ??= theme.orientation; + loading ??= theme.loading; + loadingText ??= theme.loadingText ?? _defaultLoadingText; + + Widget widget = PlatformWidget( + material: (context, platform) => + loading ?? + Container( + margin: const EdgeInsets.all(50.0), + padding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(radius!)), + child: ClipRect( + child: orientation == ProgressOrientation.vertical + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40.0, + height: 40.0, + margin: EdgeInsets.only(bottom: 8.0), + padding: EdgeInsets.all(4.0), + child: CircularProgressIndicator(strokeWidth: 3.0), + ), + Text(loadingText!, + style: textStyle!.copyWith(color: Colors.white), textAlign: TextAlign.center), + ], + ) + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 36.0, + height: 36.0, + margin: EdgeInsets.only(right: 8.0), + padding: EdgeInsets.all(4.0), + child: CircularProgressIndicator(strokeWidth: 3.0), + ), + Text(loadingText!, + style: textStyle!.copyWith(color: Colors.white), textAlign: TextAlign.center), + ], + ), + ), + ), + cupertino: (context, platform) => + loading ?? + CupertinoPopupSurface( + isSurfacePainted: true, + child: Container( + margin: const EdgeInsets.all(20.0), + child: ClipRect( + child: orientation == ProgressOrientation.vertical + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40.0, + height: 40.0, + padding: EdgeInsets.all(4.0), + child: CupertinoActivityIndicator(), + ), + Text(loadingText!, + style: textStyle, textAlign: TextAlign.center), + ], + ) + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 36.0, + height: 36.0, + padding: EdgeInsets.all(4.0), + child: CupertinoActivityIndicator(), + ), + Text(loadingText!, + style: textStyle, textAlign: TextAlign.center), + ], + ), + ), + ), + ), + ); + + return showProgressDialogWidget( + widget, + context: context, + onDismiss: onDismiss, + textDirection: textDirection, + ); +} + +/// show [widget] with progress dialog +ProgressFuture showProgressDialogWidget( + Widget widget, { + BuildContext? context, + VoidCallback? onDismiss, + TextDirection? textDirection, + bool? handleTouch, +}) { + context ??= _contextMap.values.first; + OverlayEntry entry; + ProgressFuture future; + + var direction = textDirection ?? _ProgressTheme.of(context)!.textDirection; + + GlobalKey<_ProgressContainerState> key = GlobalKey(); + + widget = Align( + child: widget, + alignment: Alignment.center, + ); + + entry = OverlayEntry(builder: (ctx) { + return IgnorePointer( + ignoring: true, + child: _ProgressContainer( + key: key, + child: Directionality(textDirection: direction, child: widget), + ), + ); + }); + + // only one progress dialog at a time is showing + ProgressManager().dismissAll(); + + future = ProgressFuture._(entry, onDismiss, key); + + Overlay.of(context).insert(entry); + ProgressManager().addFuture(future); + return future; +} + +/// use the method to dismiss all progress dialog. +void dismissProgressDialog({bool showAnim = true}) { + ProgressManager().dismissAll(showAnim: showAnim); +} diff --git a/.vendor/flutter_progress_dialog/lib/src/widget/container.dart b/.vendor/flutter_progress_dialog/lib/src/widget/container.dart new file mode 100644 index 00000000..99f06c94 --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/widget/container.dart @@ -0,0 +1,72 @@ +part of '../progress_dialog.dart'; + +class _ProgressContainer extends StatefulWidget { + final Widget? child; + + const _ProgressContainer({ + Key? key, + this.child, + }) : super(key: key); + + @override + _ProgressContainerState createState() => _ProgressContainerState(); +} + +class _ProgressContainerState extends State<_ProgressContainer> + with WidgetsBindingObserver { + double opacity = 0.0; + + @override + void initState() { + super.initState(); + Future.delayed(const Duration(milliseconds: 30), () { + if (!mounted) { + return; + } + setState(() { + opacity = 1.0; + }); + }); + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + if (this.mounted) { + setState(() {}); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget w = AnimatedOpacity( + duration: _opacityDuration, + child: widget.child, + opacity: opacity, + ); + + var mediaQueryData = MediaQueryData.fromView(View.of(context)); + Widget container = w; + + var edgeInsets = EdgeInsets.only(bottom: mediaQueryData.viewInsets.bottom); + container = AnimatedPadding( + duration: _opacityDuration, + padding: edgeInsets, + child: container, + ); + return container; + } + + void showDismissAnim() { + setState(() { + opacity = 0.0; + }); + } +} diff --git a/.vendor/flutter_progress_dialog/lib/src/widget/progress.dart b/.vendor/flutter_progress_dialog/lib/src/widget/progress.dart new file mode 100644 index 00000000..0995fc28 --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/widget/progress.dart @@ -0,0 +1,92 @@ +part of '../progress_dialog.dart'; + +class ProgressDialog extends StatefulWidget { + final Widget child; + final Widget? loading; + final String? loadingText; + final TextStyle? textStyle; + final Color backgroundColor; + final double radius; + final TextDirection? textDirection; + final ProgressOrientation? orientation; + + const ProgressDialog({ + Key? key, + required this.child, + this.loading, + this.loadingText, + this.textStyle, + this.radius = 10.0, + Color? backgroundColor, + this.textDirection, + this.orientation, + }) : this.backgroundColor = backgroundColor ?? const Color(0xDD000000), + super(key: key); + + @override + _ProgressDialogState createState() => _ProgressDialogState(); +} + +class _ProgressDialogState extends State { + @override + void dispose() { + _contextMap.remove(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var overlay = Overlay( + initialEntries: [ + OverlayEntry( + builder: (ctx) { + _contextMap[this] = ctx; + return widget.child; + }, + ), + ], + ); + + TextDirection direction = widget.textDirection ?? TextDirection.ltr; + + Widget w = Directionality( + child: Stack(children: [ + overlay, + Positioned( + left: 0.0, + right: 0.0, + top: 0.0, + bottom: 0.0, + child: IgnorePointer( + child: Container(color: Colors.black.withValues(alpha: 0.0)), + ), + ) + ]), + textDirection: direction, + ); + + return _ProgressTheme( + child: w, + backgroundColor: widget.backgroundColor, + radius: widget.radius, + loading: widget.loading, + loadingText: widget.loadingText, + textDirection: direction, + orientation: widget.orientation, + ); + } +} + +class ProgressVisibleObserver extends NavigatorObserver { + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + dismissProgressDialog(); + } + + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + dismissProgressDialog(); + } +} diff --git a/.vendor/flutter_progress_dialog/lib/src/widget/theme.dart b/.vendor/flutter_progress_dialog/lib/src/widget/theme.dart new file mode 100644 index 00000000..e88ca09a --- /dev/null +++ b/.vendor/flutter_progress_dialog/lib/src/widget/theme.dart @@ -0,0 +1,37 @@ +part of '../progress_dialog.dart'; + +class _ProgressTheme extends InheritedWidget { + final Color? backgroundColor; + final double? radius; + final TextDirection textDirection; + final ProgressOrientation? orientation; + final String? loadingText; + final Widget? loading; + + const _ProgressTheme({ + this.backgroundColor, + this.radius, + this.orientation, + this.loading, + this.loadingText, + TextDirection? textDirection, + required Widget child, + }) : textDirection = textDirection ?? TextDirection.ltr, + super(child: child); + + static const origin = _ProgressTheme( + child: const SizedBox(), + backgroundColor: const Color(0xDD000000), + radius: 10.0, + orientation: ProgressOrientation.horizontal, + textDirection: TextDirection.ltr, + loading: null, + loadingText: "Loading...", + ); + + static _ProgressTheme? of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType<_ProgressTheme>(); + + @override + bool updateShouldNotify(InheritedWidget oldWidget) => true; +} diff --git a/.vendor/flutter_progress_dialog/publish.sh b/.vendor/flutter_progress_dialog/publish.sh new file mode 100644 index 00000000..4c65cbef --- /dev/null +++ b/.vendor/flutter_progress_dialog/publish.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +flutter format . +flutter packages pub publish --dry-run +flutter packages pub publish -v \ No newline at end of file diff --git a/.vendor/flutter_progress_dialog/pubspec.yaml b/.vendor/flutter_progress_dialog/pubspec.yaml new file mode 100644 index 00000000..b245e45c --- /dev/null +++ b/.vendor/flutter_progress_dialog/pubspec.yaml @@ -0,0 +1,18 @@ +name: flutter_progress_dialog +description: A flutter library for the progress dialog. Easily show and hide. Support specify custom loading widget. +version: 0.2.0 +homepage: https://github.com/wuzhendev/flutter_progress_dialog + +environment: + sdk: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" + +dependencies: + flutter: + sdk: flutter + flutter_platform_widgets: ^9.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 diff --git a/.vendor/flutter_progress_dialog/test/flutter_progress_dialog_test.dart b/.vendor/flutter_progress_dialog/test/flutter_progress_dialog_test.dart new file mode 100644 index 00000000..705c5287 --- /dev/null +++ b/.vendor/flutter_progress_dialog/test/flutter_progress_dialog_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Placeholder test + test('placeholder', () { + expect(true, isTrue); + }); +} diff --git a/.vendor/flutter_secure_storage_linux/CHANGELOG.md b/.vendor/flutter_secure_storage_linux/CHANGELOG.md new file mode 100644 index 00000000..950bb87e --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/CHANGELOG.md @@ -0,0 +1,37 @@ +## 3.0.0 +- Fixed whitespace deprecation warning. +- Reverted json.dump with indentations due to problems. If still needed, pin version to 2.x + +## 2.0.1 +Adds application ID to cmake file + +## 2.0.0 +- This plugin requires a minimum dart sdk of 3.3.0 or higher and a minimum flutter version of 3.19.0. +- Updated documentation + +## 1.2.2 +- Fix json.dump with indentations + +## 1.2.1 +- Fixed search with schemas fails in cold keyrings +- Fixed erase called on null + +## 1.2.0 +- Remove and replace libjsoncpp1 dependency +- Update Dart SDK Constraint to support <4.0.0 instead of <3.0.0. + +## 1.1.3 +Fixed a memory management issue + +## 1.1.2 +Updated flutter_secure_storage_platform_interface to latest version. + +## 1.1.1 +Fixed an issue where no error was being reported if there was something wrong accessing the secret service. + +## 1.1.0 +Add containsKey function. + +## 1.0.0 +- Initial Linux implementation +- Removed unused Flutter test and effective_dart dependency \ No newline at end of file diff --git a/.vendor/flutter_secure_storage_linux/LICENSE b/.vendor/flutter_secure_storage_linux/LICENSE new file mode 100644 index 00000000..defe7c85 --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright 2017 German Saprykin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/.vendor/flutter_secure_storage_linux/README.md b/.vendor/flutter_secure_storage_linux/README.md new file mode 100644 index 00000000..cc5d08cb --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/README.md @@ -0,0 +1,25 @@ +# flutter_secure_storage_linux + +This is the platform-specific implementation of `flutter_secure_storage` for Linux. + +## Features + +- Secure storage using `libsecret` library. +- Compatible with various Linux keyring services like Gnome Keyring and KDE KWalletManager. + +## Installation + +Ensure you have the required dependencies installed: `libsecret-1-dev` and `libjsoncpp-dev`. + +## Configuration + +1. Install a keyring service such as [`gnome-keyring`](https://wiki.gnome.org/Projects/GnomeKeyring) or [`kwalletmanager`](https://wiki.archlinux.org/title/KDE_Wallet). +2. Ensure your application includes runtime dependencies like `libsecret-1-0` and `libjsoncpp1`. + +## Usage + +Refer to the main [flutter_secure_storage README](../README.md) for common usage instructions. + +## License + +This project is licensed under the BSD 3 License. See the [LICENSE](../LICENSE) file for details. diff --git a/.vendor/flutter_secure_storage_linux/linux/CMakeLists.txt b/.vendor/flutter_secure_storage_linux/linux/CMakeLists.txt new file mode 100644 index 00000000..eb7afe95 --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "flutter_secure_storage_linux") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "flutter_secure_storage_linux_plugin") + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +add_library(${PLUGIN_NAME} SHARED + "flutter_secure_storage_linux_plugin.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) + +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) + + +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE +"${CMAKE_CURRENT_SOURCE_DIR}/include") +include_directories(${LIBSECRET_INCLUDE_DIRS}) + +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::LIBSECRET) + +# List of absolute paths to libraries that should be bundled with the plugin +set(flutter_secure_storage_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/.vendor/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc b/.vendor/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc new file mode 100644 index 00000000..8e5eed00 --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc @@ -0,0 +1,198 @@ +#include "include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h" +#include "include/Secret.hpp" +#include "include/json.hpp" + +#include +#include +#include +#include +#include + +#define flutter_secure_storage_linux_plugin(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_secure_storage_linux_plugin_get_type(), \ + FlutterSecureStorageLinuxPlugin)) + +struct _FlutterSecureStorageLinuxPlugin +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE(FlutterSecureStorageLinuxPlugin, flutter_secure_storage_linux_plugin, + g_object_get_type()) + +static SecretStorage keyring; +void deleteIt(const gchar *key) { keyring.deleteItem(key); } +void deleteAll() { keyring.deleteKeyring(); } +void write(const gchar *key, const gchar *value) +{ + keyring.addItem(key, value); +} + +FlValue *read(const gchar *key) +{ + auto str = keyring.getItem(key); + if (str == "") + { + return nullptr; + } + return fl_value_new_string(str.c_str()); +} + +FlValue *readAll() +{ + FlValue *result = fl_value_new_map(); + nlohmann::json data = keyring.readFromKeyring(); + for (auto each : data.items()) + { + fl_value_set_string_take(result, each.key().c_str(), + fl_value_new_string(std::string(each.value()).c_str())); + } + return result; +} + +FlValue* containsKey(const gchar* key) { + nlohmann::json data = keyring.readFromKeyring(); + return fl_value_new_bool(data.contains(key)); +} + +// Called when a method call is received from Flutter. +static void flutter_secure_storage_linux_plugin_handle_method_call( + FlutterSecureStorageLinuxPlugin *self, FlMethodCall *method_call) +{ + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar *method = fl_method_call_get_name(method_call); + FlValue *args = fl_method_call_get_args(method_call); + + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) + { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "Bad arguments", "args given to function is not a map", nullptr)); + } + else + { + FlValue *key = fl_value_lookup_string(args, "key"); + FlValue *value = fl_value_lookup_string(args, "value"); + const gchar *keyString = + key == nullptr ? nullptr : fl_value_get_string(key); + const gchar *valueString = + value == nullptr ? nullptr : fl_value_get_string(value); + + try + { + if (strcmp(method, "write") == 0) + { + if (!keyString || !valueString) + { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "Bad arguments", "Key or Value was null", nullptr)); + } + else + { + write(keyString, valueString); + response = + FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + } + else if (strcmp(method, "read") == 0) + { + if (!keyString) + { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "Bad arguments", "Key is null", nullptr)); + } + else + { + g_autoptr(FlValue) result = read(keyString); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } + } + else if (strcmp(method, "readAll") == 0) + { + g_autoptr(FlValue) result = readAll(); + response = + FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } + else if (strcmp(method, "delete") == 0) + { + if (!keyString) + { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "Bad arguments", "Key is null", nullptr)); + } + else + { + deleteIt(keyString); + response = + FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + } + else if (strcmp(method, "deleteAll") == 0) + { + deleteAll(); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + else if (strcmp(method, "containsKey") == 0) { + if (!keyString) { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "Bad arguments", "Key is null", nullptr)); + } else { + g_autoptr(FlValue) result = containsKey(keyString); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } + } + else + { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + } + catch (const gchar *e) + { + g_warning("libsecret_error: %s", e); + response = FL_METHOD_RESPONSE( + fl_method_error_response_new("Libsecret error", e, nullptr)); + } + fl_method_call_respond(method_call, response, nullptr); + } +} + +static void flutter_secure_storage_linux_plugin_dispose(GObject *object) +{ + G_OBJECT_CLASS(flutter_secure_storage_linux_plugin_parent_class)->dispose(object); +} + +static void flutter_secure_storage_linux_plugin_class_init( + FlutterSecureStorageLinuxPluginClass *klass) +{ + G_OBJECT_CLASS(klass)->dispose = flutter_secure_storage_linux_plugin_dispose; +} + +static void +flutter_secure_storage_linux_plugin_init(FlutterSecureStorageLinuxPlugin *self) {} + +static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call, + gpointer user_data) +{ + FlutterSecureStorageLinuxPlugin *plugin = flutter_secure_storage_linux_plugin(user_data); + flutter_secure_storage_linux_plugin_handle_method_call(plugin, method_call); +} + +void flutter_secure_storage_linux_plugin_register_with_registrar( + FlPluginRegistrar *registrar) +{ + FlutterSecureStorageLinuxPlugin *plugin = flutter_secure_storage_linux_plugin( + g_object_new(flutter_secure_storage_linux_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + fl_plugin_registrar_get_messenger(registrar), + "plugins.it_nomads.com/flutter_secure_storage", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + channel, method_call_cb, g_object_ref(plugin), g_object_unref); + g_autofree gchar *label = + g_strdup_printf("%s/FlutterSecureStorage", APPLICATION_ID); + g_autofree gchar *account = g_strdup_printf("%s.secureStorage", APPLICATION_ID); + keyring.setLabel(label); + keyring.addAttribute("account", account); + g_object_unref(plugin); +} diff --git a/.vendor/flutter_secure_storage_linux/linux/include/FHashTable.hpp b/.vendor/flutter_secure_storage_linux/linux/include/FHashTable.hpp new file mode 100644 index 00000000..6278910e --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/include/FHashTable.hpp @@ -0,0 +1,34 @@ +#include + +class FHashTable { + GHashTable *m_hashTable; + public: + + FHashTable() { m_hashTable = g_hash_table_new_full(g_str_hash, nullptr, g_free, g_free); } + + GHashTable* getGHashTable(){ + return m_hashTable; + } + + bool insert(const char *key, const char *value) { + return g_hash_table_insert(m_hashTable, (void *)g_strdup(key), (void *)g_strdup(value)); + } + + const char *get(const char *key) { + return (const char *)g_hash_table_lookup(m_hashTable, (void *)key); + } + + bool contains(const char *key) { + return g_hash_table_contains(m_hashTable, (void *)key); + } + + bool remove(const char *key) { + return g_hash_table_remove(m_hashTable, (void *)key); + } + + void removeAll() { g_hash_table_remove_all(m_hashTable); } + + ~FHashTable() { + g_hash_table_destroy(m_hashTable); + } +}; \ No newline at end of file diff --git a/.vendor/flutter_secure_storage_linux/linux/include/Secret.hpp b/.vendor/flutter_secure_storage_linux/linux/include/Secret.hpp new file mode 100644 index 00000000..aa38aaa0 --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/include/Secret.hpp @@ -0,0 +1,121 @@ +#include "FHashTable.hpp" +#include "json.hpp" +#include +#include + +#define secret_autofree _GLIB_CLEANUP(secret_cleanup_free) +static inline void secret_cleanup_free(gchar **p) { secret_password_free(*p); } + +class SecretStorage { + FHashTable m_attributes; + std::string label; + SecretSchema the_schema; + +public: + const char *getLabel() { return label.c_str(); } + void setLabel(const char *label) { this->label = label; } + + SecretStorage(const char *_label = "default") : label(_label) { + the_schema = {label.c_str(), + SECRET_SCHEMA_NONE, + { + {"account", SECRET_SCHEMA_ATTRIBUTE_STRING}, + }}; + } + + void addAttribute(const char *key, const char *value) { + m_attributes.insert(key, value); + } + + bool addItem(const char *key, const char *value) { + nlohmann::json root = readFromKeyring(); + root[key] = value; + return storeToKeyring(root); + } + + std::string getItem(const char *key) { + std::string result; + nlohmann::json root = readFromKeyring(); + nlohmann::json value = root[key]; + if(value.is_string()){ + result = value.get(); + return result; + } + return ""; + } + + void deleteItem(const char *key) { + try { + nlohmann::json root = readFromKeyring(); + if (root.is_null()) { + return; + } + root.erase(key); + storeToKeyring(root); + } catch (const std::exception& e) { + return; + } + } + + bool deleteKeyring() { return this->storeToKeyring(nlohmann::json()); } + + bool storeToKeyring(nlohmann::json value) { + const std::string output = value.dump(); + g_autoptr(GError) err = nullptr; + bool result = secret_password_storev_sync( + &the_schema, m_attributes.getGHashTable(), nullptr, label.c_str(), + output.c_str(), nullptr, &err); + + if (err) { + throw err->message; + } + + return result; + } + + nlohmann::json readFromKeyring() { + nlohmann::json value; + g_autoptr(GError) err = nullptr; + + warmupKeyring(); + + secret_autofree gchar *result = secret_password_lookupv_sync( + &the_schema, m_attributes.getGHashTable(), nullptr, &err); + + if (err) { + throw err->message; + } + if(result != NULL && strcmp(result, "") != 0){ + value = nlohmann::json::parse(result); + } + return value; + } + +private: + // Search with schemas fails in cold keyrings. + // https://gitlab.gnome.org/GNOME/gnome-keyring/-/issues/89 + // + // Note that we're not using the workaround mentioned in the above issue. Instead, we're using + // a workaround as implemented in http://crbug.com/660005. Reason being that with the lookup + // approach we can't distinguish whether the keyring was actually unlocked or whether the user + // cancelled the password prompt. + void warmupKeyring() { + g_autoptr(GError) err = nullptr; + + FHashTable attributes; + attributes.insert("explanation", "Because of quirks in the gnome libsecret API, " + "flutter_secret_storage needs to store a dummy entry to guarantee that " + "this keyring was properly unlocked. More details at http://crbug.com/660005."); + + const gchar* dummy_label = "FlutterSecureStorage Control"; + + // Store a dummy entry without `the_schema`. + bool success = secret_password_storev_sync( + NULL, attributes.getGHashTable(), nullptr, dummy_label, + "The meaning of life", nullptr, &err); + + if (!success) { + throw "Failed to unlock the keyring"; + } + } +}; diff --git a/.vendor/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h b/.vendor/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h new file mode 100644 index 00000000..506ae73c --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_LINUX_PLUGIN_H_ +#define FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_LINUX_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _FlutterSecureStorageLinuxPlugin FlutterSecureStorageLinuxPlugin; +typedef struct +{ + GObjectClass parent_class; +} FlutterSecureStorageLinuxPluginClass; + +FLUTTER_PLUGIN_EXPORT GType flutter_secure_storage_linux_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void flutter_secure_storage_linux_plugin_register_with_registrar( + FlPluginRegistrar *registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_PLUGIN_H_ diff --git a/.vendor/flutter_secure_storage_linux/linux/include/json.hpp b/.vendor/flutter_secure_storage_linux/linux/include/json.hpp new file mode 100644 index 00000000..14945d1b --- /dev/null +++ b/.vendor/flutter_secure_storage_linux/linux/include/json.hpp @@ -0,0 +1,24674 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template