diff --git a/.all-contributorsrc b/.all-contributorsrc index da88e3369..33a5556f6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -493,7 +493,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/1270149?v=4", "profile": "https://uekoetter.dev/", "contributions": [ - "doc" + "doc", + "code" ] }, { @@ -747,6 +748,123 @@ "contributions": [ "code" ] + }, + { + "login": "gmackall", + "name": "Gray Mackall", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "profile": "https://github.com/gmackall", + "contributions": [ + "code" + ] + }, + { + "login": "p-mazhnik", + "name": "Pavel Mazhnik", + "avatar_url": "https://avatars.githubusercontent.com/u/25964451?v=4", + "profile": "https://github.com/p-mazhnik", + "contributions": [ + "code" + ] + }, + { + "login": "nnnlog", + "name": "nlog (solrin)", + "avatar_url": "https://avatars.githubusercontent.com/u/20399222?v=4", + "profile": "https://nlog.dev", + "contributions": [ + "code" + ] + }, + { + "login": "Murmurl912", + "name": "Murmurl912", + "avatar_url": "https://avatars.githubusercontent.com/u/36264246?v=4", + "profile": "https://github.com/Murmurl912", + "contributions": [ + "code" + ] + }, + { + "login": "bschulz87", + "name": "Benjamin Schulz", + "avatar_url": "https://avatars.githubusercontent.com/u/30199362?v=4", + "profile": "https://github.com/bschulz87", + "contributions": [ + "ideas" + ] + }, + { + "login": "ShuheiSuzuki-07", + "name": "seal-app", + "avatar_url": "https://avatars.githubusercontent.com/u/118415919?v=4", + "profile": "https://github.com/ShuheiSuzuki-07", + "contributions": [ + "code" + ] + }, + { + "login": "takuyaaaaaaahaaaaaa", + "name": "Takuya Tominaga", + "avatar_url": "https://avatars.githubusercontent.com/u/31458194?v=4", + "profile": "https://github.com/takuyaaaaaaahaaaaaa", + "contributions": [ + "code" + ] + }, + { + "login": "yamaha252", + "name": "Sergey", + "avatar_url": "https://avatars.githubusercontent.com/u/4444068?v=4", + "profile": "https://github.com/yamaha252", + "contributions": [ + "code" + ] + }, + { + "login": "lyb5834", + "name": "yuanbo li", + "avatar_url": "https://avatars.githubusercontent.com/u/16265810?v=4", + "profile": "https://github.com/lyb5834", + "contributions": [ + "code" + ] + }, + { + "login": "Mecharyry", + "name": "Ryan Feline", + "avatar_url": "https://avatars.githubusercontent.com/u/3380092?v=4", + "profile": "https://github.com/Mecharyry", + "contributions": [ + "code" + ] + }, + { + "login": "fuzzybinary", + "name": "Jeff Ward", + "avatar_url": "https://avatars.githubusercontent.com/u/249982?v=4", + "profile": "https://fuzzybinary.com", + "contributions": [ + "test" + ] + }, + { + "login": "yerkejs", + "name": "Yelzhan Yerkebulan", + "avatar_url": "https://avatars.githubusercontent.com/u/33483071?v=4", + "profile": "https://hero.io", + "contributions": [ + "code" + ] + }, + { + "login": "GooRingX", + "name": "GooRingX", + "avatar_url": "https://avatars.githubusercontent.com/u/167741400?v=4", + "profile": "https://github.com/GooRingX", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/ISSUE_TEMPLATE/APP_SHOWCASE.md b/.github/ISSUE_TEMPLATE/APP_SHOWCASE.md deleted file mode 100755 index 169f5961d..000000000 --- a/.github/ISSUE_TEMPLATE/APP_SHOWCASE.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: App Showcase -about: Add your App on the Official Showcase Page - ---- - -Check the [Showcase](https://inappwebview.dev/showcase/) page to see an open list of Apps built with **Flutter** and **Flutter InAppWebView**. - -If you are using the **Flutter InAppWebView** plugin and would like to add your App there, -follow the instruction on the [Submit App](https://inappwebview.dev/submit-app/) page! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md deleted file mode 100755 index 62863a346..000000000 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: Bug report -about: Something is crashing or not working as intended -labels: bug ---- - - - -- [x] I have read the [Getting Started](https://inappwebview.dev/docs/intro/) section -- [x] I have already searched for the same problem - -## Environment - -| Technology | Version | -|-----------------------| ------------- | -| Flutter version | | -| Plugin version | | -| Android version | | -| iOS version | | -| macOS version | | -| Xcode version | | -| Google Chrome version | | - -Device information: - -## Description - -**Expected behavior:** - -**Current behavior:** - -## Steps to reproduce - - - -1. This -2. Than that -3. Then - -## Images - -## Stacktrace/Logcat diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md deleted file mode 100755 index 97a1f1708..000000000 --- a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -## Environment - -**Flutter version:** -**Plugin version:** -**Android version:** -**iOS version:** -**Xcode version:** -**Device information:** - -## Description - -**What you'd like to happen:** - -**Alternatives you've considered:** - -**Images:** diff --git a/.github/ISSUE_TEMPLATE/app_showcase.yml b/.github/ISSUE_TEMPLATE/app_showcase.yml new file mode 100644 index 000000000..5cd6f9e3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/app_showcase.yml @@ -0,0 +1,93 @@ +name: 📱 App Showcase +description: Add your App on the Official Showcase plugin website page +title: "Write your App name here" +labels: + - showcase + +body: + - type: markdown + attributes: + value: | + **Request to submit your application to the official [Showcase](https://inappwebview.dev/showcase) plugin website page.** + + **Please fill out the following information:** + - type: textarea + attributes: + label: App Icon + description: | + Add your App Icon here. You can drag and drop the image directly inside this textarea. + validations: + required: true + - type: textarea + attributes: + label: What is the reason of using flutter_inappwebview in your app? + description: | + Explain why you are using the flutter_inappwebview plugin in your app. + placeholder: | + I'm using the flutter_inappwebview plugin in my app to create a custom Browser, to use WebRTC feature, to display local HTML content, etc. + validations: + required: true + - type: checkboxes + attributes: + label: Platforms + description: | + Your App is available on which platforms? + options: + - label: Mobile + - label: Desktop + - label: Web + - type: input + attributes: + label: Short Description + description: | + A short description of your app. Max 250 characters. + validations: + required: true + - type: textarea + attributes: + label: Long Description + description: | + A long description of your app. + validations: + required: true + - type: textarea + attributes: + label: App Screenshots + description: | + Add some screenshots of your app. You can drag and drop images directly inside this textarea. Max 8 images. + validations: + required: true + - type: input + attributes: + label: App Website URL + description: | + Your App's official website URL, if available. + validations: + required: false + - type: input + attributes: + label: App Source Code Repository URL + description: | + Your App's source code repository URL, if available. + validations: + required: false + - type: input + attributes: + label: Google Play Store URL + description: | + Your App's Google Play Store URL, if available. + validations: + required: false + - type: input + attributes: + label: Apple App Store URL + description: | + Your App's Apple App Store URL, if available. + validations: + required: false + - type: textarea + attributes: + label: Additional information + description: Anything else you'd like to include? + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..112af4088 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,107 @@ +name: 🐛 Bug +description: Something is crashing or not working as intended +title: "Write the title here" +labels: + - bug + +body: + - type: markdown + attributes: + value: | + **Before you submit this issue, please make sure you have read the the [Getting Started](https://inappwebview.dev/docs/intro/) page.** + + **Please fill out the following information:** + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: | + PLEASE! Make sure to check if this issue is a duplicate. + + Search for it using the GitHub issue search box (check the closed issues too) or on the official [inappwebview.dev](https://inappwebview.dev/) website, or using Google, StackOverflow, etc. before posting a new one. + + You may already find an answer to your problem! + options: + - label: I have searched the existing issues + required: true + - type: textarea + attributes: + label: Current Behavior + description: Write what you are experiencing currently. + placeholder: | + The plugin isn't working as expected. It crashes when I do this... + validations: + required: true + - type: textarea + attributes: + label: Expected Behavior + description: Write what you expected to happen. + placeholder: | + The plugin should do this when I do that... + validations: + required: true + - type: textarea + attributes: + label: Steps with code example to reproduce + description: Steps with code example to reproduce the issue. A not well written description might lead to the delay in fixing the issue. + value: | +
+ Steps with code example to reproduce + + ```dart + // Paste your code here + ``` +
+ validations: + required: true + - type: textarea + attributes: + label: Stacktrace/Logs + description: | + If you have any stacktrace or logs, paste them here. Make sure to remove any sensitive information. + value: | +
+ Stacktrace/Logs + + ``` + + ``` +
+ validations: + required: true + - type: input + attributes: + label: Flutter version + description: The Flutter version in which you used the plugin to face the issue. + placeholder: v3.22.0, v3.24.3, etc. + validations: + required: true + - type: textarea + attributes: + label: Operating System, Device-specific and/or Tool + description: | + The Operating System, Device-specific and/or Tool in which you used the plugin to face the issue. + For example: All platforms, Android, iOS, XCode, macOS, Windows, Web, Chrome, etc. + Make sure to include the version too. + placeholder: Android 34+, iOS 17.0+, macOS, Windows, etc. + validations: + required: true + - type: input + attributes: + label: Plugin version + description: In which version of flutter_inappwebview did you encounter this bug? + placeholder: v6.1.0, v6.0.0, etc. + validations: + required: true + - type: textarea + attributes: + label: Additional information + description: Anything else you'd like to include? Like screenshots or a video recording of the issue. + validations: + required: false + - type: checkboxes + attributes: + label: Self grab + description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions! + options: + - label: I'm ready to work on this issue! + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..5b7de1e17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/pichillilorenzo/flutter_inappwebview/discussions/new?category=q-a + about: The place for questions and support \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..914aa71ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,54 @@ +name: ✨ Feature Request +description: Suggest an idea for this project +title: "Write the title here" +labels: + - enhancement + +body: + - type: checkboxes + attributes: + label: Is there an existing feature request for this? + description: Make sure to check if this feature request is a duplicate. + options: + - label: I have searched the existing feature request + required: true + - type: input + attributes: + label: Operating System + description: | + The Operating System in which you want to have this feature. + For example: All platforms if possible, Android, iOS, macOS, Windows, Web, Chrome, etc. + placeholder: All platforms if possible, Android, iOS, macOS, Windows, etc. + validations: + required: true + - type: textarea + attributes: + label: Pain + description: Explain your feature request. + placeholder: I'd like to have that / I don't like that I've to do this + validations: + required: true + - type: textarea + attributes: + label: Suggested solution + description: Tell about a solution you can think of + placeholder: You could add that / change this / use that + validations: + required: true + - type: textarea + attributes: + label: Useful resources + description: Provide some useful resource which could help implement your suggestion. + - type: textarea + attributes: + label: Additional information + description: Anything else you'd like to include? Like screenshots or a video recording of the issue. + validations: + required: false + - type: checkboxes + attributes: + label: Self grab + description: If you are a developer and want to work on this feature request yourself, you can check this box and wait for maintainer response. We welcome contributions! + options: + - label: I'm ready to work on this issue! + required: false \ No newline at end of file diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml index bcd2f4b82..b591d96a3 100644 --- a/.github/autolabeler.yml +++ b/.github/autolabeler.yml @@ -1,8 +1,13 @@ # Configuration for probot-auto-labeler - https://github.com/probot/autolabeler # label: file | path -android: ["/android", "*.kt", "*.java", "*gradle*", "AndroidManifest.xml"] -iOS: ["/ios", "*.swift", "*.h", "*.m", "*.xcodeproj", "*.xcworkspace", "*.plist", "*.storyboard", "*.xcconfig", Podfile*"] -flutter/dart: ["/lib", "*.dart"] +android: ["/flutter_inappwebview_android", "*.dart", "*.kt", "*.java", "*gradle*", "AndroidManifest.xml"] +iOS: ["/flutter_inappwebview_ios", "*.dart", "*.swift", "*.h", "*.m", "*.plist", "*.storyboard", "*.xcconfig", Podfile*"] +macOS: ["/flutter_inappwebview_macos", "*.dart", "*.swift", "*.h", "*.m", "*.plist", "*.storyboard", "*.xcconfig", Podfile*"] +windows: ["/flutter_inappwebview_windows", "*.dart", "*.cs", "*.c", "*.cpp", "*.h", "CMakeLists.txt"] +linux: ["/flutter_inappwebview_linux", "*.dart", "*.c", "*.h", "*.cpp", "*.cmake", "*.makefile", "CMakeLists.txt"] +web: ["/flutter_inappwebview_web", "*.dart", "*.html", "*.js", "*.css"] +platform_interface: ["/flutter_inappwebview_platform_interface", "*.dart"] +plugin: ["/flutter_inappwebview", "*.dart"] documentation: ["*.md"] legal: ["LICENSE*", "NOTICES*"] diff --git a/.github/workflows/android-integration-test.yml b/.github/workflows/android-integration-test.yml deleted file mode 100644 index 1dd606ffb..000000000 --- a/.github/workflows/android-integration-test.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Name of your workflow. -name: Android Integration Tests -on: - # Trigger the workflow on push or pull request, - # but only for the main branch - push: - branches: - - master - pull_request: - branches: - - master -# A workflow run is made up of one or more jobs. -jobs: - # id of job, a string that is unique to the "jobs" node above. - android_integration_tests: - # Creates a build matrix for your jobs. You can define different - # variations of an environment to run each job in. - strategy: - # A set of different configurations of the virtual - # environment. - # matrix: - # When set to true, GitHub cancels all in-progress jobs if any - # matrix job fails. - fail-fast: false - # The type of machine to run the job on. - runs-on: macOS-latest - timeout-minutes: 60 - # Contains a sequence of tasks. - steps: - # The branch or tag ref that triggered the workflow will be - # checked out. - # https://github.com/actions/checkout - - uses: actions/checkout@v2 - # Sets up cache - - name: Cache multiple paths - uses: actions/cache@v2 - with: - path: | - ~/.pub-cache - ~/.npm - key: ${{ runner.os }}-pub-and-npm-cache - # Sets up a flutter environment. - # https://github.com/marketplace/actions/flutter-action - - name: "Install Flutter" - uses: subosito/flutter-action@v1.4.0 - with: - channel: 'dev' # 'stable' or 'dev' or 'beta' - - name: "Change Flutter channel to master" - run: | - flutter channel master - flutter upgrade - - uses: actions/setup-node@v2 - with: - node-version: '14' - - name: "Install npm dependencies" - run: | - cd ./nodejs_server_test_auth_basic_and_ssl - npm install - cd .. - - name: "Install flutter dependencies" - run: | - flutter pub get - # Sets up android emulator - - name: "Run Android Flutter Integration Test" - uses: ReactiveCircus/android-emulator-runner@v2.14.3 - with: - api-level: 29 - target: default - avd-name: Flutter-Android - script: ./scripts/test.sh $(ipconfig getifaddr en0) \ No newline at end of file diff --git a/.github/workflows/auto-comment.yml b/.github/workflows/auto-comment.yml deleted file mode 100644 index 47f0a8953..000000000 --- a/.github/workflows/auto-comment.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Auto Comment -on: [issues, pull_request] -jobs: - run: - runs-on: ubuntu-latest - steps: - - uses: bubkoo/auto-comment@v1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - issuesOpened: > - 👋 @{{ author }} - - - **NOTE**: This comment is auto-generated. - - - Are you sure you have already searched for the same problem? - - - Some people open new issues but they didn't search for something similar or for the same issue. - Please, search for it using the GitHub issue search box or on the official [inappwebview.dev](https://inappwebview.dev/) website, - or, also, using Google, StackOverflow, etc. before posting a new one. - You may already find an answer to your problem! - - - If this is really a new issue, then thank you for raising it. I will investigate it and get back to you as soon as possible. - Please, make sure you have given me as much context as possible! - Also, if you didn't already, post a code example that can replicate this issue. - - - In the meantime, you can already search for some possible solutions online! - Because this plugin uses native WebView, you can search online for the same issue adding `android WebView [MY ERROR HERE]` or `ios WKWebView [MY ERROR HERE]` keywords. - - - Following these steps can save you, me, and other people a lot of time, thanks! \ No newline at end of file diff --git a/.github/workflows/ios-integration-test.yml b/.github/workflows/ios-integration-test.yml deleted file mode 100644 index cb790158b..000000000 --- a/.github/workflows/ios-integration-test.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Name of your workflow. -name: iOS Integration Tests -on: - # Trigger the workflow on push or pull request, - # but only for the main branch - push: - branches: - - master - pull_request: - branches: - - master -# A workflow run is made up of one or more jobs. -jobs: - # id of job, a string that is unique to the "jobs" node above. - ios_integration_tests: - # Creates a build matrix for your jobs. You can define different - # variations of an environment to run each job in. - strategy: - # A set of different configurations of the virtual - # environment. - # matrix: - # When set to true, GitHub cancels all in-progress jobs if any - # matrix job fails. - fail-fast: false - # The type of machine to run the job on. - runs-on: macOS-latest - timeout-minutes: 60 - # Contains a sequence of tasks. - steps: - # A name for your step to display on GitHub. - - name: "Start Simulator" - run: | - xcrun simctl list - xcrun simctl create Flutter-iPhone com.apple.CoreSimulator.SimDeviceType.iPhone-12 com.apple.CoreSimulator.SimRuntime.iOS-14-3 | xargs xcrun simctl boot - # The branch or tag ref that triggered the workflow will be - # checked out. - # https://github.com/actions/checkout - - uses: actions/checkout@v2 - # Sets up cache - - name: Cache multiple paths - uses: actions/cache@v2 - with: - path: | - ~/.pub-cache - ~/.npm - key: ${{ runner.os }}-pub-and-npm-cache - # Sets up a flutter environment. - # https://github.com/marketplace/actions/flutter-action - - name: "Install Flutter" - uses: subosito/flutter-action@v1.4.0 - with: - channel: 'dev' # 'stable' or 'dev' or 'beta' - - name: "Change Flutter channel to master" - run: | - flutter channel master - flutter upgrade - - uses: actions/setup-node@v2 - with: - node-version: '14' - - name: "Install npm dependencies" - run: | - cd ./nodejs_server_test_auth_basic_and_ssl - npm install - cd .. - - name: "Run iOS Flutter Integration Test" - run: | - flutter pub get - flutter devices - ./scripts/test.sh $(ipconfig getifaddr en0) \ No newline at end of file diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml new file mode 100644 index 000000000..e4f6f4126 --- /dev/null +++ b/.github/workflows/lock.yaml @@ -0,0 +1,29 @@ +# Configuration for Lock Threads - https://github.com/dessant/lock-threads + +name: 'Lock Threads' + +# By specifying the access of one of the scopes, all of those that are not +# specified are set to 'none'. +permissions: + issues: write + +on: + schedule: + - cron: '0 0 * * *' # Run every day at midnight + +jobs: + lock: + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v5.0.1 + with: + process-only: 'issues' + github-token: ${{ github.token }} + # Number of days of inactivity before a closed issue is locked. + issue-inactive-days: 14 + issue-comment: > + This thread has been automatically locked since there has not been + any recent activity after it was closed. If you are still experiencing a + similar issue, please open a new bug and a minimal reproduction of the issue. \ No newline at end of file diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yaml similarity index 78% rename from .github/workflows/no-response.yml rename to .github/workflows/no-response.yaml index cad68e988..b353a9728 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yaml @@ -1,3 +1,4 @@ + name: No Response # Both `issue_comment` and `scheduled` event types are required for this Action @@ -6,8 +7,7 @@ on: issue_comment: types: [created] schedule: - # Schedule for five minutes after the hour, every hour - - cron: '5 * * * *' + - cron: '0 0 * * *' # Run every day at midnight # By specifying the access of one of the scopes, all of those that are not # specified are set to 'none'. @@ -17,7 +17,6 @@ permissions: jobs: noResponse: runs-on: ubuntu-latest - if: ${{ github.repository == 'flutter/flutter' }} steps: - uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09 with: @@ -29,12 +28,11 @@ jobs: bug for now. If you find this problem please file a new issue with the same description, - what happens, logs and the output of 'flutter doctor -v'. All system setups - can be slightly different so it's always better to open new issues and reference + what happens and logs. It's always better to open new issues and reference the related ones. Thanks for your contribution. # Number of days of inactivity before an issue is closed for lack of response. daysUntilClose: 21 # Label requiring a response. - responseRequiredLabel: "waiting for customer response" \ No newline at end of file + responseRequiredLabel: "waiting for customer response" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 000000000..13ffa0832 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,22 @@ +name: Close Stale Issues + +on: + schedule: + - cron: '0 0 * * *' # Run every day at midnight + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Close stale issues + uses: actions/stale@v9.0.0 + with: + days-before-issue-stale: 365 + days-before-issue-close: 0 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale and has been automatically closed because it has been open for more than 365 days with no activity. Please reopen a new issue if you still have it." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index cac255942..7710a3b56 100755 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock -**/doc/api/ +doc/api/ .dart_tool/ .packages build/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..70f8824fc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,75 @@ +{ + "cmake.configureOnOpen": false, + "files.associations": { + "algorithm": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "coroutine": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "set": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 1780a4557..6d63860ad 100755 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ ![InAppWebView-logo](https://user-images.githubusercontent.com/5956938/195422744-bdcfed16-73f0-4bc9-94ab-ecf10771a1c4.png) -[![All Contributors](https://img.shields.io/badge/all_contributors-82-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-95-orange.svg?style=flat-square)](#contributors-) -[![Pub](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) +[![flutter_inappwebview version](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) [![Pub Points](https://img.shields.io/pub/points/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) [![Pub Popularity](https://img.shields.io/pub/popularity/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) [![Pub Likes](https://img.shields.io/pub/likes/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) @@ -19,13 +19,18 @@ [![GitHub forks](https://img.shields.io/github/forks/pichillilorenzo/flutter_inappwebview?style=social)](https://github.com/pichillilorenzo/flutter_inappwebview) [![GitHub stars](https://img.shields.io/github/stars/pichillilorenzo/flutter_inappwebview?style=social)](https://github.com/pichillilorenzo/flutter_inappwebview) -A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. +###### Supported Platforms - +[![flutter_inappwebview_platform_interface version](https://img.shields.io/pub/v/flutter_inappwebview_platform_interface?include_prereleases&label=Platform%20Interface)](https://pub.dartlang.org/packages/flutter_inappwebview_platform_interface) +[![flutter_inappwebview_android version](https://img.shields.io/pub/v/flutter_inappwebview_android?include_prereleases&label=Android)](https://pub.dartlang.org/packages/flutter_inappwebview_android) +[![flutter_inappwebview_ios version](https://img.shields.io/pub/v/flutter_inappwebview_ios?include_prereleases&label=iOS)](https://pub.dartlang.org/packages/flutter_inappwebview_ios) +[![flutter_inappwebview_macos version](https://img.shields.io/pub/v/flutter_inappwebview_macos?include_prereleases&label=macOS)](https://pub.dartlang.org/packages/flutter_inappwebview_macos) +[![flutter_inappwebview_windows version](https://img.shields.io/pub/v/flutter_inappwebview_windows?include_prereleases&label=Windows)](https://pub.dartlang.org/packages/flutter_inappwebview_windows) +[![flutter_inappwebview_web version](https://img.shields.io/pub/v/flutter_inappwebview_web?include_prereleases&label=Web)](https://pub.dartlang.org/packages/flutter_inappwebview_web) -## New Version 6.x.x is OUT NOW! +A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -Migrating from version `5.x.x` is easy! Follow the online [Migration guide](https://inappwebview.dev/docs/migration-guide). + ## Articles/Resources @@ -47,27 +52,23 @@ Send a submission request to the [Submit App](https://inappwebview.dev/submit-ap ## Requirements -- Dart sdk: ">=2.17.0 <4.0.0" -- Flutter: ">=3.0.0" +- Dart sdk: "^3.5.0" +- Flutter: ">=3.24.0" - Android: `minSdkVersion >= 19`, `compileSdk >= 34`, [AGP](https://developer.android.com/build/releases/gradle-plugin) version `>= 7.3.0` (use [Android Studio - Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant) for help), support for `androidx` (see [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration) to migrate an existing app) -- iOS 9.0+: `--ios-language swift`, Xcode version `>= 14.3` -- MacOS 10.11+: Xcode version `>= 14.3` +- iOS 12.0+: `--ios-language swift`, Xcode version `>= 15.0` +- MacOS 10.14+: Xcode version `>= 15.0` +- Windows: [NuGet CLI](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools?tabs=windows#nugetexe-cli) available on your PATH environment variable ## Installation Add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). -### Installation - Web support - -To make it work properly on the Web platform, you need to add the `web_support.js` file inside the `` of your `web/index.html` file: - -```html - - - - - -``` +### Platform Installation Setup: +- [Android](https://inappwebview.dev/docs/intro/#setup-android) +- [iOS](https://inappwebview.dev/docs/intro/#setup-ios) +- [macOS](https://inappwebview.dev/docs/intro/#setup-macos) +- [Windows](https://inappwebview.dev/docs/intro/#setup-windows) +- [Web](https://inappwebview.dev/docs/intro/#setup-web) ## Support @@ -150,7 +151,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Rex Raphael
Rex Raphael

💻 Jan Henrik Høiland
Jan Henrik Høiland

💻 Iguchi Tomokatsu
Iguchi Tomokatsu

💻 - Jonas Uekötter
Jonas Uekötter

📖 + Jonas Uekötter
Jonas Uekötter

📖 💻 emakar
emakar

💻 liasica
liasica

💻 @@ -187,6 +188,23 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d lrorpilla
lrorpilla

💻 Michal Šrůtek
Michal Šrůtek

💻 daisukeueta
daisukeueta

💻 + Gray Mackall
Gray Mackall

💻 + Pavel Mazhnik
Pavel Mazhnik

💻 + + + nlog (solrin)
nlog (solrin)

💻 + Murmurl912
Murmurl912

💻 + Benjamin Schulz
Benjamin Schulz

🤔 + seal-app
seal-app

💻 + Takuya Tominaga
Takuya Tominaga

💻 + Sergey
Sergey

💻 + yuanbo li
yuanbo li

💻 + + + Ryan Feline
Ryan Feline

💻 + Jeff Ward
Jeff Ward

⚠️ + Yelzhan Yerkebulan
Yelzhan Yerkebulan

💻 + GooRingX
GooRingX

💻 diff --git a/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md b/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md index b7586c319..81ce60287 100755 --- a/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md +++ b/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 + +- Updated `ExchangeableEnum` and `ExchangeableObjectProperty`. + ## 1.1.1 - Added `ExchangeableObject.fromMapForceAllInline`. diff --git a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart index b69902497..ea66483b4 100644 --- a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart +++ b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart @@ -4,6 +4,10 @@ class ExchangeableEnum { final bool fromValueMethod; final bool toNativeValueMethod; final bool fromNativeValueMethod; + final bool nameMethod; + final bool toNameMethod; + final bool byNameMethod; + final bool asNameMapMethod; final bool toStringMethod; final bool hashCodeMethod; final bool equalsOperator; @@ -15,6 +19,10 @@ class ExchangeableEnum { this.fromValueMethod = true, this.toNativeValueMethod = true, this.fromNativeValueMethod = true, + this.nameMethod = true, + this.toNameMethod = true, + this.byNameMethod = true, + this.asNameMapMethod = true, this.toStringMethod = true, this.hashCodeMethod = true, this.equalsOperator = true, diff --git a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart index 14fa2eae5..9ffd83955 100644 --- a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart +++ b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart @@ -1,9 +1,17 @@ class ExchangeableObjectProperty { final Function? serializer; final Function? deserializer; + final bool deprecatedUseNewFieldNameInConstructor; + final bool deprecatedUseNewFieldNameInFromMapMethod; + final bool leaveDeprecatedInFromMapMethod; + final bool leaveDeprecatedInToMapMethod; const ExchangeableObjectProperty({ this.serializer, - this.deserializer + this.deserializer, + this.deprecatedUseNewFieldNameInConstructor = true, + this.deprecatedUseNewFieldNameInFromMapMethod = true, + this.leaveDeprecatedInFromMapMethod = false, + this.leaveDeprecatedInToMapMethod = false, }); } diff --git a/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml b/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml index 215aca267..64e918ec7 100755 --- a/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml +++ b/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_internal_annotations description: Internal annotations used by the generator of flutter_inappwebview plugin -version: 1.1.1 +version: 1.2.0 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: diff --git a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart index c362e6812..2d602bba7 100644 --- a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart @@ -140,6 +140,7 @@ class ExchangeableEnumGenerator []; var hasWebSupport = false; var webSupportValue = null; + var allPlatformsWithoutValue = true; if (platforms.isNotEmpty) { for (var platform in platforms) { final targetPlatformName = @@ -150,6 +151,9 @@ class ExchangeableEnumGenerator ? platformValueField.toIntValue() ?? "'${platformValueField.toStringValue()}'" : null; + if (allPlatformsWithoutValue && platformValue != null) { + allPlatformsWithoutValue = false; + } if (targetPlatformName == "web") { hasWebSupport = true; webSupportValue = platformValue; @@ -170,8 +174,13 @@ class ExchangeableEnumGenerator nativeValueBody += "return $defaultValue;"; nativeValueBody += "}"; - classBuffer.writeln( - "static final $fieldName = $extClassName._internalMultiPlatform($constantValue, $nativeValueBody);"); + if (!allPlatformsWithoutValue) { + classBuffer.writeln( + "static final $fieldName = $extClassName._internalMultiPlatform($constantValue, $nativeValueBody);"); + } else { + classBuffer.writeln( + "static const $fieldName = $extClassName._internal($constantValue, ${defaultValue ?? constantValue});"); + } } else { classBuffer.writeln( "static const $fieldName = $extClassName._internal($constantValue, $constantValue);"); @@ -195,13 +204,13 @@ class ExchangeableEnumGenerator } if (annotation.read("fromValueMethod").boolValue && (!visitor.methods.containsKey("fromValue") || - Util.methodHasIgnore(visitor.methods['fromNativeValue']!))) { + Util.methodHasIgnore(visitor.methods['fromValue']!))) { final hasBitwiseOrOperator = annotation.read("bitwiseOrOperator").boolValue; classBuffer.writeln(""" ///Gets a possible [$extClassName] instance from [${enumValue.type}] value. static $extClassName? fromValue(${enumValue.type}${!Util.typeIsNullable(enumValue.type) ? '?' : ''} value) { - if (value != null) { + if (value != null) { try { return $extClassName.values .firstWhere((element) => element.toValue() == value); @@ -221,7 +230,7 @@ class ExchangeableEnumGenerator classBuffer.writeln(""" ///Gets a possible [$extClassName] instance from a native value. static $extClassName? fromNativeValue(${enumNativeValue.type}${!Util.typeIsNullable(enumNativeValue.type) ? '?' : ''} value) { - if (value != null) { + if (value != null) { try { return $extClassName.values .firstWhere((element) => element.toNativeValue() == value); @@ -234,6 +243,44 @@ class ExchangeableEnumGenerator """); } + if (annotation.read("nameMethod").boolValue && annotation.read("byNameMethod").boolValue && + (!visitor.methods.containsKey("byName") || Util.methodHasIgnore(visitor.methods['byName']!))) { + classBuffer.writeln(""" + /// Gets a possible [$extClassName] instance value with name [name]. + /// + /// Goes through [$extClassName.values] looking for a value with + /// name [name], as reported by [$extClassName.name]. + /// Returns the first value with the given name, otherwise `null`. + static $extClassName? byName(String? name) { + if (name != null) { + try { + return $extClassName.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + """); + } + + if (annotation.read("nameMethod").boolValue && annotation.read("asNameMapMethod").boolValue && + (!visitor.methods.containsKey("asNameMap") || Util.methodHasIgnore(visitor.methods['asNameMap']!))) { + classBuffer.writeln(""" + /// Creates a map from the names of [$extClassName] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + {for (final value in $extClassName.values) value.name(): value}; + """); + } + for (final entry in methodEntriesSorted) { final methodElement = entry.value; if (Util.methodHasIgnore(methodElement)) { @@ -273,6 +320,28 @@ class ExchangeableEnumGenerator """); } + if (annotation.read("nameMethod").boolValue && (!visitor.methods.containsKey("name") || + Util.methodHasIgnore(visitor.methods['name']!))) { + classBuffer.writeln('///Gets the name of the value.'); + classBuffer.writeln('String name() {'); + classBuffer.writeln('switch(_value) {'); + for (final entry in fieldEntriesSorted) { + final fieldName = entry.key; + final fieldElement = entry.value; + if (!fieldElement.isPrivate && fieldElement.isStatic) { + final fieldValue = fieldElement.computeConstantValue()?.getField("_value"); + dynamic constantValue = fieldValue?.toIntValue(); + if (enumValue.type.isDartCoreString) { + constantValue = "'${fieldValue?.toStringValue()}'"; + } + classBuffer.writeln("case $constantValue: return '$fieldName';"); + } + } + classBuffer.writeln('}'); + classBuffer.writeln('return _value.toString();'); + classBuffer.writeln('}'); + } + if (annotation.read("hashCodeMethod").boolValue && (!visitor.fields.containsKey("hashCode") || Util.methodHasIgnore(visitor.methods['hashCode']!))) { classBuffer.writeln(""" @@ -306,19 +375,23 @@ class ExchangeableEnumGenerator if (enumValue.type.isDartCoreString) { classBuffer.writeln('return _value;'); } else { - classBuffer.writeln('switch(_value) {'); - for (final entry in fieldEntriesSorted) { - final fieldName = entry.key; - final fieldElement = entry.value; - if (!fieldElement.isPrivate && fieldElement.isStatic) { - final fieldValue = - fieldElement.computeConstantValue()?.getField("_value"); - final constantValue = fieldValue?.toIntValue(); - classBuffer.writeln("case $constantValue: return '$fieldName';"); + if (annotation.read("nameMethod").boolValue) { + classBuffer.writeln('return name();'); + } else { + classBuffer.writeln('switch(_value) {'); + for (final entry in fieldEntriesSorted) { + final fieldName = entry.key; + final fieldElement = entry.value; + if (!fieldElement.isPrivate && fieldElement.isStatic) { + final fieldValue = + fieldElement.computeConstantValue()?.getField("_value"); + final constantValue = fieldValue?.toIntValue(); + classBuffer.writeln("case $constantValue: return '$fieldName';"); + } } + classBuffer.writeln('}'); + classBuffer.writeln('return _value.toString();'); } - classBuffer.writeln('}'); - classBuffer.writeln('return _value.toString();'); } classBuffer.writeln('}'); } diff --git a/dev_packages/generators/lib/src/exchangeable_object_generator.dart b/dev_packages/generators/lib/src/exchangeable_object_generator.dart index 1b3d935c6..498c95aa9 100644 --- a/dev_packages/generators/lib/src/exchangeable_object_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_object_generator.dart @@ -1,9 +1,10 @@ -import 'package:build/src/builder/build_step.dart'; -import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:source_gen/source_gen.dart'; +import 'package:build/src/builder/build_step.dart'; +import 'package:collection/collection.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'package:source_gen/source_gen.dart'; import 'model_visitor.dart'; import 'util.dart'; @@ -224,29 +225,43 @@ class ExchangeableObjectGenerator if (!hasCustomConstructor && deprecatedFields.length > 0) { classBuffer.writeln(' {'); for (final deprecatedField in deprecatedFields) { - final deprecatedFieldName = deprecatedField.name; + final deprecatedUseNewFieldNameInConstructor = _coreCheckerObjectProperty + .firstAnnotationOf(deprecatedField) + ?.getField("deprecatedUseNewFieldNameInConstructor") + ?.toBoolValue() ?? true; + if (!deprecatedUseNewFieldNameInConstructor) { + continue; + } + final message = _coreCheckerDeprecated .firstAnnotationOfExact(deprecatedField)! .getField("message")! - .toStringValue()!; - final fieldName = message + .toStringValue()!.trim(); + if (!message.startsWith("Use ") && !message.endsWith(" instead")) { + continue; + } + + final newFieldName = message .replaceFirst("Use ", "") .replaceFirst(" instead", "") .trim(); - final fieldElement = visitor.fields[fieldName]; - if (fieldElement != null) { - final fieldTypeElement = fieldElement.type.element; + + final newFieldElement = visitor.fields[newFieldName]; + final shouldUseNewFieldName = newFieldElement != null; + if (shouldUseNewFieldName) { + final deprecatedFieldName = deprecatedField.name; + final fieldTypeElement = newFieldElement.type.element; final deprecatedFieldTypeElement = deprecatedField.type.element; - final isNullable = Util.typeIsNullable(fieldElement.type); - var hasDefaultValue = (fieldElement is ParameterElement) - ? (fieldElement as ParameterElement).hasDefaultValue + final isNullable = Util.typeIsNullable(newFieldElement.type); + var hasDefaultValue = (newFieldElement is ParameterElement) + ? (newFieldElement as ParameterElement).hasDefaultValue : false; if (!isNullable && hasDefaultValue) { continue; } - classBuffer.write('$fieldName = $fieldName ?? '); + classBuffer.write('$newFieldName = $newFieldName ?? '); if (fieldTypeElement != null && deprecatedFieldTypeElement != null) { final deprecatedIsNullable = Util.typeIsNullable(deprecatedField.type); @@ -274,7 +289,7 @@ class ExchangeableObjectGenerator } else if (deprecatedField.type .getDisplayString(withNullability: false) == "Uri" && - fieldElement.type.getDisplayString(withNullability: false) == + newFieldElement.type.getDisplayString(withNullability: false) == "WebUri") { if (deprecatedIsNullable) { classBuffer.write( @@ -304,7 +319,8 @@ class ExchangeableObjectGenerator final nullable = annotation.read("nullableFromMapFactory").boolValue; classBuffer .writeln('static $extClassName${nullable ? '?' : ''} fromMap('); - classBuffer.writeln('Map${nullable ? '?' : ''} map'); + classBuffer.writeln( + 'Map${nullable ? '?' : ''} map, {EnumMethod? enumMethod}'); classBuffer.writeln(') {'); if (nullable) { classBuffer.writeln('if (map == null) { return null; }'); @@ -324,18 +340,59 @@ class ExchangeableObjectGenerator !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { var value = "map['$fieldName']"; - final deprecationMessage = _coreCheckerDeprecated - .firstAnnotationOfExact(fieldElement) - ?.getField("message") - ?.toStringValue(); - if (deprecationMessage != null) { - final newFieldName = deprecationMessage - .replaceFirst("Use ", "") - .replaceFirst(" instead", "") - .trim(); - value = "map['$newFieldName']"; + + if (fieldElement.hasDeprecated) { + final deprecatedUseNewFieldNameInFromMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("deprecatedUseNewFieldNameInFromMapMethod") + ?.toBoolValue() ?? true; + + final deprecationMessage = _coreCheckerDeprecated + .firstAnnotationOfExact(fieldElement) + ?.getField("message") + ?.toStringValue()?.trim(); + if (deprecationMessage != null && + deprecationMessage.startsWith("Use ") && + deprecationMessage.endsWith(" instead") && + deprecatedUseNewFieldNameInFromMapMethod) { + final newFieldName = deprecationMessage + .replaceFirst("Use ", "") + .replaceFirst(" instead", "") + .trim(); + final newFieldElement = fieldElements + .firstWhereOrNull((element) => element.name == newFieldName); + final shouldUseNewFieldName = newFieldElement != null && + (newFieldElement.type == fieldElement.type || + (fieldElement.name.startsWith(RegExp(r'android|ios')) && + fieldElement.name.toLowerCase().replaceFirst( + RegExp(r'android|ioswk|ios'), "") == + newFieldName.toLowerCase()) || + (newFieldElement.type.element != null && + fieldElement.type.element != null && + ((hasFromNativeValueMethod( + newFieldElement.type.element!) && + hasFromNativeValueMethod( + fieldElement.type.element!) || + (hasFromMapMethod( + newFieldElement.type.element!) && + hasFromMapMethod( + fieldElement.type.element!)))))); + if (shouldUseNewFieldName) { + value = "map['$newFieldName']"; + } + } else { + final leaveDeprecatedInFromMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("leaveDeprecatedInFromMapMethod") + ?.toBoolValue() ?? false; + if (!leaveDeprecatedInFromMapMethod) { + continue; + } + } } + final mapValue = value; + final customDeserializer = _coreCheckerObjectProperty .firstAnnotationOf(fieldElement) ?.getField("deserializer") @@ -345,9 +402,10 @@ class ExchangeableObjectGenerator customDeserializer.enclosingElement.name; if (deserializerClassName != null) { value = - "$deserializerClassName.${customDeserializer.name}($value)"; + "$deserializerClassName.${customDeserializer.name}($value, enumMethod: enumMethod)"; } else { - value = "${customDeserializer.name}($value)"; + value = + "${customDeserializer.name}($value, enumMethod: enumMethod)"; } } else { value = getFromMapValue(value, fieldElement.type); @@ -355,13 +413,23 @@ class ExchangeableObjectGenerator final constructorParameter = visitor.constructorParameters[fieldName]; final isRequiredParameter = constructorParameter != null && (constructorParameter.isRequiredNamed || - constructorParameter.isFinal || fieldElement.isFinal || + constructorParameter.isFinal || + fieldElement.isFinal || !Util.typeIsNullable(constructorParameter.type)) && !constructorParameter.hasDefaultValue; - if (isRequiredParameter || fieldElement.isFinal || annotation.read("fromMapForceAllInline").boolValue) { + if (isRequiredParameter || + fieldElement.isFinal || + annotation.read("fromMapForceAllInline").boolValue) { requiredFields.add('$fieldName: $value,'); } else { + final isFieldNullable = Util.typeIsNullable(fieldElement.type); + if (!isFieldNullable) { + nonRequiredFields.add("if ($mapValue != null) {"); + } nonRequiredFields.add("instance.$fieldName = $value;"); + if (!isFieldNullable) { + nonRequiredFields.add("}"); + } } } } @@ -401,13 +469,12 @@ class ExchangeableObjectGenerator (!visitor.methods.containsKey("toMap") || Util.methodHasIgnore(visitor.methods['toMap']!))) { classBuffer.writeln('///Converts instance to a map.'); - classBuffer.writeln('Map toMap() {'); + classBuffer.writeln('Map toMap({EnumMethod? enumMethod}) {'); classBuffer.writeln('return {'); final fieldElements = []; if (superClass != null) { for (final fieldElement in superClass.element.fields) { if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { @@ -418,7 +485,6 @@ class ExchangeableObjectGenerator for (final entry in fieldEntriesSorted) { final fieldElement = entry.value; if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { @@ -427,10 +493,19 @@ class ExchangeableObjectGenerator } for (final fieldElement in fieldElements) { if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { + if (fieldElement.hasDeprecated) { + final leaveDeprecatedInToMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("leaveDeprecatedInToMapMethod") + ?.toBoolValue() ?? false; + if (!leaveDeprecatedInToMapMethod) { + continue; + } + } + final fieldName = fieldElement.name; var mapValue = fieldName; final customSerializer = _coreCheckerObjectProperty @@ -441,9 +516,9 @@ class ExchangeableObjectGenerator final serializerClassName = customSerializer.enclosingElement.name; if (serializerClassName != null) { mapValue = - "$serializerClassName.${customSerializer.name}($mapValue)"; + "$serializerClassName.${customSerializer.name}($mapValue, enumMethod: enumMethod)"; } else { - mapValue = "${customSerializer.name}($mapValue)"; + mapValue = "${customSerializer.name}($mapValue, enumMethod: enumMethod)"; } } else { mapValue = getToMapValue(fieldName, fieldElement.type); @@ -459,7 +534,7 @@ class ExchangeableObjectGenerator ?.getField("toMapMergeWith") ?.toBoolValue(); if (toMapMergeWith == true) { - classBuffer.writeln('...${methodElement.name}(),'); + classBuffer.writeln('...${methodElement.name}(enumMethod: enumMethod),'); } } classBuffer.writeln('};'); @@ -526,6 +601,8 @@ class ExchangeableObjectGenerator String getFromMapValue(String value, DartType elementType) { final fieldTypeElement = elementType.element; + // remove class reference terminating with "_" + final classNameReference = fieldTypeElement?.name?.replaceFirst("_", ""); final isNullable = Util.typeIsNullable(elementType); final displayString = elementType.getDisplayString(withNullability: false); if (displayString == "Uri") { @@ -556,6 +633,12 @@ class ExchangeableObjectGenerator } else { return "$value != null ? DateTime.fromMillisecondsSinceEpoch($value) : null"; } + } else if (displayString == "Uint8List") { + if (!isNullable) { + return "Uint8List.fromList($value.cast())"; + } else { + return "$value != null ? Uint8List.fromList($value.cast()) : null"; + } } else if (elementType.isDartCoreList || elementType.isDartCoreSet) { final genericTypes = Util.getGenericTypes(elementType); final genericType = genericTypes.isNotEmpty ? genericTypes.first : null; @@ -586,23 +669,36 @@ class ExchangeableObjectGenerator return "$value${isNullable ? '?' : ''}.cast<${genericTypes.elementAt(0)}, ${genericTypes.elementAt(1)}>()"; } else if (fieldTypeElement != null && hasFromMapMethod(fieldTypeElement)) { final hasNullableFromMap = hasNullableFromMapFactory(fieldTypeElement); - // remove class reference terminating with "_" - return fieldTypeElement.name!.replaceFirst("_", "") + - ".fromMap($value?.cast())${!isNullable && hasNullableFromMap ? '!' : ''}"; + return classNameReference! + + ".fromMap($value?.cast(), enumMethod: enumMethod)${!isNullable && hasNullableFromMap ? '!' : ''}"; } else { - final hasFromValue = - fieldTypeElement != null && hasFromValueMethod(fieldTypeElement); - final hasFromNativeValue = fieldTypeElement != null && - hasFromNativeValueMethod(fieldTypeElement); - if (fieldTypeElement != null && (hasFromValue || hasFromNativeValue)) { - if (hasFromNativeValue) { - // remove class reference terminating with "_" - value = fieldTypeElement.name!.replaceFirst("_", "") + - '.fromNativeValue($value)'; + final hasFromValue = fieldTypeElement != null && hasFromValueMethod(fieldTypeElement); + final hasFromNativeValue = fieldTypeElement != null && hasFromNativeValueMethod(fieldTypeElement); + final hasByName = fieldTypeElement != null && hasByNameMethod(fieldTypeElement); + if (fieldTypeElement != null && (hasFromValue || hasFromNativeValue || hasByName)) { + if ([hasFromValue, hasFromNativeValue, hasByName].where((e) => e).length > 1) { + String? defaultEnumMethodValue = null; + if (hasFromNativeValue) { + defaultEnumMethodValue = "EnumMethod.nativeValue"; + } else if (hasFromValue) { + defaultEnumMethodValue = "EnumMethod.value"; + } else { + defaultEnumMethodValue = "EnumMethod.name"; + } + var wrapper = "switch (enumMethod ?? $defaultEnumMethodValue) {"; + wrapper += "EnumMethod.nativeValue => " + (hasFromNativeValue ? classNameReference! + '.fromNativeValue($value)' : "null") + ", "; + wrapper += "EnumMethod.value => " + (hasFromValue ? classNameReference! + '.fromValue($value)' : "null") + ", "; + wrapper += "EnumMethod.name => " + (hasByName ? classNameReference! + '.byName($value)' : "null"); + wrapper += "}"; + value = wrapper; } else { - // remove class reference terminating with "_" - value = fieldTypeElement.name!.replaceFirst("_", "") + - '.fromValue($value)'; + if (hasFromNativeValue) { + value = classNameReference! + '.fromNativeValue($value)'; + } else if (hasFromValue) { + value = classNameReference! + '.fromValue($value)'; + } else { + value = classNameReference! + '.byName($value)'; + } } if (!isNullable) { value += '!'; @@ -647,17 +743,35 @@ class ExchangeableObjectGenerator } else if (fieldTypeElement != null && hasToMapMethod(fieldTypeElement)) { return fieldName + (Util.typeIsNullable(elementType) ? '?' : '') + - '.toMap()'; + '.toMap(enumMethod: enumMethod)'; } else { - final hasToValue = - fieldTypeElement != null && hasToValueMethod(fieldTypeElement); - final hasToNativeValue = - fieldTypeElement != null && hasToNativeValueMethod(fieldTypeElement); - if (fieldTypeElement != null && (hasToValue || hasToNativeValue)) { - if (hasToNativeValue) { - return fieldName + (isNullable ? '?' : '') + '.toNativeValue()'; + final hasToValue = fieldTypeElement != null && hasToValueMethod(fieldTypeElement); + final hasToNativeValue = fieldTypeElement != null && hasToNativeValueMethod(fieldTypeElement); + final hasName = fieldTypeElement != null && hasNameMethod(fieldTypeElement); + if (fieldTypeElement != null && (hasToValue || hasToNativeValue || hasName)) { + if ([hasToValue, hasToNativeValue, hasName].where((e) => e).length > 1) { + String? defaultEnumMethodValue = null; + if (hasToNativeValue) { + defaultEnumMethodValue = "EnumMethod.nativeValue"; + } else if (hasToValue) { + defaultEnumMethodValue = "EnumMethod.value"; + } else { + defaultEnumMethodValue = "EnumMethod.name"; + } + var wrapper = "switch (enumMethod ?? $defaultEnumMethodValue) {"; + wrapper += "EnumMethod.nativeValue => " + (hasToNativeValue ? (fieldName + (isNullable ? '?' : '') + '.toNativeValue()') : "null") + ", "; + wrapper += "EnumMethod.value => " + (hasToValue ? (fieldName + (isNullable ? '?' : '') + '.toValue()') : "null") + ", "; + wrapper += "EnumMethod.name => " + (hasName ? (fieldName + (isNullable ? '?' : '') + '.name()') : "null"); + wrapper += "}"; + return wrapper; } else { - return fieldName + (isNullable ? '?' : '') + '.toValue()'; + if (hasToNativeValue) { + return fieldName + (isNullable ? '?' : '') + '.toNativeValue()'; + } else if (hasToValue) { + return fieldName + (isNullable ? '?' : '') + '.toValue()'; + } else { + return fieldName + (isNullable ? '?' : '') + '.name()'; + } } } } @@ -781,6 +895,28 @@ class ExchangeableObjectGenerator return false; } + bool hasByNameMethod(Element element) { + final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); + final byNameMethod = _coreCheckerEnum + .firstAnnotationOfExact(element) + ?.getField('byNameMethod') + ?.toBoolValue() ?? + false; + if (hasAnnotation && byNameMethod) { + return true; + } + + final fieldVisitor = ModelVisitor(); + element.visitChildren(fieldVisitor); + for (var entry in fieldVisitor.methods.entries) { + if (entry.key == "byName") { + return true; + } + } + + return false; + } + bool hasToValueMethod(Element element) { final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); final hasToValueMethod = _coreCheckerEnum @@ -824,4 +960,26 @@ class ExchangeableObjectGenerator return false; } + + bool hasNameMethod(Element element) { + final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); + final hasNameMethod = _coreCheckerEnum + .firstAnnotationOfExact(element) + ?.getField('nameMethod') + ?.toBoolValue() ?? + false; + if (hasAnnotation && hasNameMethod) { + return true; + } + + final fieldVisitor = ModelVisitor(); + element.visitChildren(fieldVisitor); + for (var entry in fieldVisitor.methods.entries) { + if (entry.key == "name") { + return true; + } + } + + return false; + } } diff --git a/dev_packages/generators/pubspec.yaml b/dev_packages/generators/pubspec.yaml index be0fb8110..19bff4858 100755 --- a/dev_packages/generators/pubspec.yaml +++ b/dev_packages/generators/pubspec.yaml @@ -4,17 +4,19 @@ version: 1.0.0 publish_to: none environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - build: ^2.4.0 - source_gen: ^1.3.1 - flutter_inappwebview_internal_annotations: ^1.1.1 + build: ^2.4.1 + source_gen: ^1.5.0 + collection: any + flutter_inappwebview_internal_annotations: ^1.2.0 + # path: ../flutter_inappwebview_internal_annotations dev_dependencies: - build_runner: ^2.4.0 - build_test: ^2.1.7 - test: ^1.24.2 \ No newline at end of file + build_runner: ^2.4.12 + build_test: ^2.2.2 + test: ^1.25.8 \ No newline at end of file diff --git a/flutter_inappwebview/CHANGELOG.md b/flutter_inappwebview/CHANGELOG.md index 6bfb046d8..b45f23ceb 100755 --- a/flutter_inappwebview/CHANGELOG.md +++ b/flutter_inappwebview/CHANGELOG.md @@ -1,3 +1,231 @@ +## 6.2.0-beta.3 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.4.0-beta.2` -> `^1.4.0-beta.3` + - `flutter_inappwebview_android`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_ios`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_macos`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_web`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_windows`: `^0.7.0-beta.2` -> `^0.7.0-beta.3` +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +#### Platform Interface +- Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` +- Added `onShowFileChooser` WebView events + +#### Android Platform +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event +- Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) + +#### macOS and iOS Platforms +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Merged "Add proxy support for iOS" [#2362](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2362) (thanks to [yerkejs](https://github.com/yerkejs)) +- Fixed "[iOS] Webview opened with windowId does not receive javascript handler callback." [#2393](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2393) +- Fixed internal javascript callback handlers when the WebView has windowId not null +- macos: Fixed crash of unhandled `onPrintRequest` WebView event + +### Windows +- Merged "windows: fix WebViewEnvironment dispose crash" [#2433](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2433) (thanks to [GooRingX](https://github.com/GooRingX)) + +## 6.2.0-beta.2 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.4.0-beta.1` -> `^1.4.0-beta.2` + - `flutter_inappwebview_android`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_ios`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_macos`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_web`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_windows`: `^0.7.0-beta.1` -> `^0.7.0-beta.2` +- Fixed specific URLAuthenticationChallenge type for `onReceivedHttpAuthRequest`, `onReceivedServerTrustAuthRequest`, `onReceivedClientCertRequest` events of HeadlessInAppWebView +- Fixed missing return type for `InAppWebViewController.getJavaScriptBridgeName` static method + +#### Platform Interface +- Updated `flutter_inappwebview_internal_annotations` dependency from `^1.1.1` to `^1.2.0` +- Updated `fromMap` static method and `toMap` method implementations +- Updated all WebView events with return type `Future` to type `FutureOr` in order to not force the usage of `async` keyword +- Added `byName`, `name`, `asNameMap` custom enum classes methods +- Added `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled`, `alpha`, `isUserInteractionEnabled` properties to `InAppWebViewSettings` +- Added `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` methods to `PlatformWebViewEnvironment` class +- Added `isInterfaceSupported`, `setInputMethodEnabled`, `hideInputMethod`, `showInputMethod` methods to `PlatformInAppWebViewController` class +- Added `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties to `WebViewEnvironmentSettings` +- Added `onDownloadStarting` WebView event and deprecated `onDownloadStartRequest` event +- Added `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` events to `PlatformWebViewEnvironment` class +- Fixed missing PrintJobOrientation android values + +#### Android Platform +- Implemented `hideInputMethod`, `showInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "[Android] PrintJobOrientation _TypeError (type 'Null' is not a subtype of type 'int')" [#2413](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2413) +- Fixed "Accessibility Android" [#1694](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1694) +- Fixed "Automatic font scale according to accessibility option 'font size' of device does not work on Android" [#540](https://github.com/pichillilorenzo/flutter_inappwebview/issues/540) +- Fixed "callHandler method is not injected into InAppBrowser" [#1973](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1973) + +#### iOS Platform +- Implemented `setInputMethodEnabled`, `hideInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "In iOS version 17.2, when moving the input focus in a WebView, an unknown area appears at the top of the screen." [#1947](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947) + +#### macOS Platform +- Implemented `alpha` property of `InAppWebViewSettings` + +#### Windows Platform +- Updated Microsoft.Web.WebView2 SDK version from `1.0.2792.45` to `1.0.2849.39` +- Implemented `disableDefaultErrorPage`, `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `allowsBackForwardNavigationGestures`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled` properties of `InAppWebViewSettings` +- Implemented `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` WebViewEnvironment methods +- Implemented `isInterfaceSupported`, `getZoomScale` InAppWebViewController method +- Implemented `onDownloadStarting`, `onAcceleratorKeyPressed` WebView event +- Implemented `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties of `WebViewEnvironmentSettings` +- Implemented `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` WebViewEnvironment events +- Send mouse leave region event to native view +- Fixed wrong channel name when creating a `WebViewEnvironment` instance +- Fixed "[Windows] Has an overlay on the desktop when the application is minimized" [#2402](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2402) +- Fixed "[Windows] missing implementation of onPermissionRequest event will cause crash when requested by the webpage" [#2404](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2404) +- Fixed "Windows: getCookies return empty list" [#2314](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2314) + +## 6.2.0-beta.1 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.3.0` -> `^1.4.0-beta.1` + - `flutter_inappwebview_android`: `^1.1.3` -> `^1.2.0-beta.1` + - `flutter_inappwebview_ios`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_macos`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_web`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_windows`: `^0.6.0` -> `^0.7.0-beta.1` +- Fixed specific URLAuthenticationChallenge type for `onReceivedHttpAuthRequest`, `onReceivedServerTrustAuthRequest`, `onReceivedClientCertRequest` events + +Implemented security features to better manage access to the native javascript bridge. + +#### Platform Interface +- Updated static `fromMap` implementation for some classes +- Updated `kJavaScriptHandlerForbiddenNames` list +- Added `PlatformInAppLocalhostServer.onData` parameter to set a custom on data server callback +- Added `javaScriptBridgeEnabled`, `javaScriptBridgeOriginAllowList`, `javaScriptBridgeForMainFrameOnly`, `pluginScriptsOriginAllowList`, `pluginScriptsForMainFrameOnly`, `javaScriptHandlersOriginAllowList`, `javaScriptHandlersForMainFrameOnly`, `scrollMultiplier` InAppWebViewSettings parameters +- Added `setJavaScriptBridgeName`, `getJavaScriptBridgeName` static WebView controller methods +- Added `requestFocus` WebView method +- Added `onProcessFailed` WebView event +- Added `regexToAllowSyncUrlLoading` Android-specific property to `InAppWebViewSettings` +- Added `JavaScriptHandlerFunctionData` type +- Deprecated `JavaScriptHandlerCallback` type in favor of `JavaScriptHandlerFunction` type +- Deprecated `InAppWebViewSettings.forceDark` and `InAppWebViewSettings.forceDarkStrategy` Android-only properties in favor of `InAppWebViewSettings.algorithmicDarkeningAllowed` +- Fixed X509Certificate PEM base64 decoding + +#### Android Platform +- Added `InAppWebViewController.enableSlowWholeDocumentDraw` static method +- Added `CookieManager.flush` method +- Added support for `UserScript.forMainFrameOnly` parameter +- Implemented `requestFocus` WebView method +- Updated UserScript at document end implementation +- Updated `InAppWebViewController.takeScreenshot` implementation to support screenshot out of visible viewport when `InAppWebViewController.enableSlowWholeDocumentDraw` is called +- Fixed "After dispose a InAppWebViewKeepAlive using InAppWebViewController.disposeKeepAlive. NullPointerException is thrown when main activity enter destroyed state." [#2025](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2025) +- Fixed crash when trying to open InAppBrowser with R.menu.menu_main on release mode +- Fixed "android.webkit.WebSettingsWrapper cannot be cast to com.android.webview.chromium.ContentSettingsAdapter" [#2397](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2397) +- Merged "Prevent blank InAppBrowser Activity from being restored" [#1984](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1984) (thanks to [ShuheiSuzuki-07](https://github.com/ShuheiSuzuki-07)) +- Merged "Update Android Cookie Expiration date format to 24-hour format (HH)" [#2389](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2389) (thanks to [takuyaaaaaaahaaaaaa](https://github.com/takuyaaaaaaahaaaaaa)) +- Merged "[Android] allow sync navigation requests using a regular expression" [#2008](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2008) (thanks to [lyb5834](https://github.com/lyb5834)) + +#### macOS and iOS Platforms +- Implemented `requestFocus` WebView method +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Added support for `UserScript.allowedOriginRules` parameter +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- ios: Fixed `show`, `hide` methods and `hidden` setting for `InAppBrowser` +- macOS: Implemented also `clearFocus` WebView method +- macOS: Implemented workaround for "[macOS] Copy Shortcut does not work if TextField outside of WebView has focus" [#2380](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380) + +#### Windows Platform +- Updated `scrollMultiplier` default value from 6 to 1 +- Added support for `UserScript.allowedOriginRules` and `UserScript.forMainFrameOnly` parameters +- Implemented `onReceivedHttpAuthRequest`, `onReceivedClientCertRequest`, `onReceivedServerTrustAuthRequest`, `onRenderProcessGone`, `onRenderProcessUnresponsive`, `onWebContentProcessDidTerminate`, `onProcessFailed` WebView events +- Implemented `clearSslPreferences` WebView method +- Fixed `get_optional_fl_map_value` implementation in `utils/flutter.h` +- Fixed "Error in transparentBackground handling in Windows" [#2391](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2391) + +#### Web Platform +- Merged "[web] support iframe role and aria-hidden attributes" [2293](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2293) (thanks to [p-mazhnik](https://github.com/p-mazhnik)) +- Fixed 'Type 'int' is not a subtype of type 'JSValue' in type cast' when compiling/running using WASM + +## 6.1.5 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_windows`: `^0.5.0` -> `^0.6.0` + +#### Windows Platform +- Updated code to support multiple flutter windows +- Fixed `InAppWebViewController.callAsyncJavaScript` not working with JSON objects +- Fixed `onLoadResourceWithCustomScheme` WebView event called every time + +## 6.1.4 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.2.0` -> `^1.3.0` + - `flutter_inappwebview_android`: `^1.1.1` -> `^1.1.3` + - `flutter_inappwebview_ios`: `^1.1.1` -> `^1.1.2` + - `flutter_inappwebview_macos`: `^1.1.1` -> `^1.1.2` + - `flutter_inappwebview_web`: `^1.1.1` -> `^1.1.2` + - `flutter_inappwebview_windows`: `^0.4.0` -> `^0.5.0` + +#### Android Platform +- Removed webview/plugin_scripts_js/ConsoleLogJS.java file, use native WebChromeClient.onConsoleMessage instead + +#### Windows Platform +- Implemented `shouldInterceptRequest`, `onLoadResourceWithCustomScheme` WebView events + +## 6.1.3 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.1.0` -> `^1.2.0` + - `flutter_inappwebview_android`: `^1.1.0+4` -> `^1.1.1` + - `flutter_inappwebview_ios`: `^1.1.0+3` -> `^1.1.1` + - `flutter_inappwebview_macos`: `^1.1.0+3` -> `^1.1.1` + - `flutter_inappwebview_web`: `^1.1.0+2` -> `^1.1.1` + - `flutter_inappwebview_windows`: `^0.3.0` -> `^0.4.0` + +#### Windows Platform + - Updated `shouldOverrideUrlLoading` implementation using the Chrome DevTools Protocol API Fetch.requestPaused event + +## 6.1.2 + +- Updated minimum platform implementation versions + +#### Windows Platform + +- Implemented `pause`, `resume`, `getCertificate` methods for `InAppWebViewController` +- Implemented `onPermissionRequest` WebView event +- Fixed `InAppWebViewController.evaluateJavascript` not working with JSON objects +- Fixed `InAppWebViewManager::METHOD_CHANNEL_NAME` c++ value +- Fixed `InAppWebViewController.takeScreenshot` to behave consistently with the other platforms + +## 6.1.1 + +- Updated README +- Updated pubspec.yaml +- Updated minimum platform implementation versions + +## 6.1.0+1 + +- Updated README + +## 6.1.0 + +- Added initial Windows support +- Added `InAppWebView` widget MacOS support +- Added privacy manifest for MacOS +- Migrated web support to `package:web`. +- Updated minimum supported SDK version to Flutter 3.24/Dart 3.5. +- Updated androidx.webkit:webkit:1.8.0 to androidx.webkit:webkit:1.12.0 +- Updated androidx.browser:browser:1.6.0 to androidx.browser:browser:1.8.0 +- Fixed "[MACOS] launching InAppBrowser with 'hidden: true' calls onExit immediately" [#1939](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939) +- Fixed XCode 16 build +- Removed unsupported WebViewFeature.SUPPRESS_ERROR_PAGE +- Merged "Add privacy manifest for iOS" [#2029](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2029) (thanks to [ueman](https://github.com/ueman)) +- Merged "Remove references to deprecated v1 Android embedding" [#2176](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2176) (thanks to [gmackall](https://github.com/gmackall)) + ## 6.0.0 - Updated minimum platform interface and implementation versions diff --git a/flutter_inappwebview/README.md b/flutter_inappwebview/README.md index 1780a4557..6d63860ad 100755 --- a/flutter_inappwebview/README.md +++ b/flutter_inappwebview/README.md @@ -5,10 +5,10 @@ ![InAppWebView-logo](https://user-images.githubusercontent.com/5956938/195422744-bdcfed16-73f0-4bc9-94ab-ecf10771a1c4.png) -[![All Contributors](https://img.shields.io/badge/all_contributors-82-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-95-orange.svg?style=flat-square)](#contributors-) -[![Pub](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) +[![flutter_inappwebview version](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) [![Pub Points](https://img.shields.io/pub/points/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) [![Pub Popularity](https://img.shields.io/pub/popularity/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) [![Pub Likes](https://img.shields.io/pub/likes/flutter_inappwebview)](https://pub.dev/packages/flutter_inappwebview/score) @@ -19,13 +19,18 @@ [![GitHub forks](https://img.shields.io/github/forks/pichillilorenzo/flutter_inappwebview?style=social)](https://github.com/pichillilorenzo/flutter_inappwebview) [![GitHub stars](https://img.shields.io/github/stars/pichillilorenzo/flutter_inappwebview?style=social)](https://github.com/pichillilorenzo/flutter_inappwebview) -A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. +###### Supported Platforms - +[![flutter_inappwebview_platform_interface version](https://img.shields.io/pub/v/flutter_inappwebview_platform_interface?include_prereleases&label=Platform%20Interface)](https://pub.dartlang.org/packages/flutter_inappwebview_platform_interface) +[![flutter_inappwebview_android version](https://img.shields.io/pub/v/flutter_inappwebview_android?include_prereleases&label=Android)](https://pub.dartlang.org/packages/flutter_inappwebview_android) +[![flutter_inappwebview_ios version](https://img.shields.io/pub/v/flutter_inappwebview_ios?include_prereleases&label=iOS)](https://pub.dartlang.org/packages/flutter_inappwebview_ios) +[![flutter_inappwebview_macos version](https://img.shields.io/pub/v/flutter_inappwebview_macos?include_prereleases&label=macOS)](https://pub.dartlang.org/packages/flutter_inappwebview_macos) +[![flutter_inappwebview_windows version](https://img.shields.io/pub/v/flutter_inappwebview_windows?include_prereleases&label=Windows)](https://pub.dartlang.org/packages/flutter_inappwebview_windows) +[![flutter_inappwebview_web version](https://img.shields.io/pub/v/flutter_inappwebview_web?include_prereleases&label=Web)](https://pub.dartlang.org/packages/flutter_inappwebview_web) -## New Version 6.x.x is OUT NOW! +A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -Migrating from version `5.x.x` is easy! Follow the online [Migration guide](https://inappwebview.dev/docs/migration-guide). + ## Articles/Resources @@ -47,27 +52,23 @@ Send a submission request to the [Submit App](https://inappwebview.dev/submit-ap ## Requirements -- Dart sdk: ">=2.17.0 <4.0.0" -- Flutter: ">=3.0.0" +- Dart sdk: "^3.5.0" +- Flutter: ">=3.24.0" - Android: `minSdkVersion >= 19`, `compileSdk >= 34`, [AGP](https://developer.android.com/build/releases/gradle-plugin) version `>= 7.3.0` (use [Android Studio - Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant) for help), support for `androidx` (see [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration) to migrate an existing app) -- iOS 9.0+: `--ios-language swift`, Xcode version `>= 14.3` -- MacOS 10.11+: Xcode version `>= 14.3` +- iOS 12.0+: `--ios-language swift`, Xcode version `>= 15.0` +- MacOS 10.14+: Xcode version `>= 15.0` +- Windows: [NuGet CLI](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools?tabs=windows#nugetexe-cli) available on your PATH environment variable ## Installation Add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). -### Installation - Web support - -To make it work properly on the Web platform, you need to add the `web_support.js` file inside the `` of your `web/index.html` file: - -```html - - - - - -``` +### Platform Installation Setup: +- [Android](https://inappwebview.dev/docs/intro/#setup-android) +- [iOS](https://inappwebview.dev/docs/intro/#setup-ios) +- [macOS](https://inappwebview.dev/docs/intro/#setup-macos) +- [Windows](https://inappwebview.dev/docs/intro/#setup-windows) +- [Web](https://inappwebview.dev/docs/intro/#setup-web) ## Support @@ -150,7 +151,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Rex Raphael
Rex Raphael

💻 Jan Henrik Høiland
Jan Henrik Høiland

💻 Iguchi Tomokatsu
Iguchi Tomokatsu

💻 - Jonas Uekötter
Jonas Uekötter

📖 + Jonas Uekötter
Jonas Uekötter

📖 💻 emakar
emakar

💻 liasica
liasica

💻 @@ -187,6 +188,23 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d lrorpilla
lrorpilla

💻 Michal Šrůtek
Michal Šrůtek

💻 daisukeueta
daisukeueta

💻 + Gray Mackall
Gray Mackall

💻 + Pavel Mazhnik
Pavel Mazhnik

💻 + + + nlog (solrin)
nlog (solrin)

💻 + Murmurl912
Murmurl912

💻 + Benjamin Schulz
Benjamin Schulz

🤔 + seal-app
seal-app

💻 + Takuya Tominaga
Takuya Tominaga

💻 + Sergey
Sergey

💻 + yuanbo li
yuanbo li

💻 + + + Ryan Feline
Ryan Feline

💻 + Jeff Ward
Jeff Ward

⚠️ + Yelzhan Yerkebulan
Yelzhan Yerkebulan

💻 + GooRingX
GooRingX

💻 diff --git a/flutter_inappwebview/example/.metadata b/flutter_inappwebview/example/.metadata index 8f5507649..ba5723a22 100755 --- a/flutter_inappwebview/example/.metadata +++ b/flutter_inappwebview/example/.metadata @@ -4,5 +4,27 @@ # This file should be version controlled and should not be manually edited. version: - revision: d927c9331005f81157fa39dff7b5dab415ad330b - channel: master + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter_inappwebview/example/android/app/build.gradle b/flutter_inappwebview/example/android/app/build.gradle index 1a5f9e50e..a50a94003 100755 --- a/flutter_inappwebview/example/android/app/build.gradle +++ b/flutter_inappwebview/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,9 +22,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { namespace 'com.pichillilorenzo.flutterwebviewexample' @@ -32,13 +30,12 @@ android { targetCompatibility 1.8 } - compileSdkVersion 34 - + compileSdk 34 defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.pichillilorenzo.flutter_inappwebviewexample" - minSdkVersion 19 + minSdkVersion flutter.minSdkVersion targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -48,6 +45,10 @@ android { buildTypes { release { + // only for com.pichillilorenzo.flutter_inappwebview_android.R.menu.menu_main + minifyEnabled false + shrinkResources false + // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug @@ -64,9 +65,9 @@ flutter { } dependencies { - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - implementation 'com.google.android.material:material:1.6.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.6.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + implementation 'com.google.android.material:material:1.12.0' implementation 'com.android.support:multidex:1.0.3' } diff --git a/flutter_inappwebview/example/android/app/src/main/AndroidManifest.xml b/flutter_inappwebview/example/android/app/src/main/AndroidManifest.xml index cc96cd822..3581175ee 100755 --- a/flutter_inappwebview/example/android/app/src/main/AndroidManifest.xml +++ b/flutter_inappwebview/example/android/app/src/main/AndroidManifest.xml @@ -20,11 +20,6 @@ - - - - - - - - - - - - - - - - - plugins.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version '8.6.1' apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false } + +include ":app" \ No newline at end of file diff --git a/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart b/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart index 4615f4573..78eb9c4dc 100644 --- a/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart +++ b/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart @@ -40,7 +40,7 @@ void openAndClose() { activityButton: ActivityButton( templateImage: UIImage(systemName: "sun.max"), extensionIdentifier: - "com.pichillilorenzo.flutterinappwebview-ios-example.test"))); + "com.pichillilorenzo.flutterinappwebview-ios-example3.test"))); await chromeSafariBrowser.opened.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { diff --git a/flutter_inappwebview/example/integration_test/headless_in_app_webview/convert_to_inappwebview.dart b/flutter_inappwebview/example/integration_test/headless_in_app_webview/convert_to_inappwebview.dart index af88f38f4..17c853d0d 100644 --- a/flutter_inappwebview/example/integration_test/headless_in_app_webview/convert_to_inappwebview.dart +++ b/flutter_inappwebview/example/integration_test/headless_in_app_webview/convert_to_inappwebview.dart @@ -26,6 +26,7 @@ void convertToInAppWebView() { expect(headlessWebView.isRunning(), true); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; final String? url = (await controller.getUrl())?.toString(); @@ -55,6 +56,7 @@ void convertToInAppWebView() { ); final InAppWebViewController widgetController = await widgetControllerCompleter.future; + await tester.pump(); expect(headlessWebView.isRunning(), false); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_content_height.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_content_height.dart index 1e1635fc9..570e8eba8 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_content_height.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_content_height.dart @@ -33,9 +33,8 @@ void getContentHeight() { ); final InAppWebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - await tester.pump(); + await pageLoaded.future; final contentHeight = await controller.getContentHeight(); expect(contentHeight, isNonZero); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_favicons.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_favicons.dart index d2ff44a21..402e61b06 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_favicons.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_favicons.dart @@ -33,6 +33,7 @@ void getFavicons() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; final List? favicons = await controller.getFavicons(); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_html.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_html.dart index a6230478f..bd405621d 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_html.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_html.dart @@ -33,6 +33,8 @@ void getHtml() { ); final InAppWebViewController controller = await controllerCompleter.future; + // Platform view creation happens asynchronously. + await tester.pump(); await pageLoaded.future; final String? html = await controller.getHtml(); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_tags.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_tags.dart index e30b703e8..c6bc57394 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_tags.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_tags.dart @@ -33,6 +33,7 @@ void getMetaTags() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; List metaTags = await controller.getMetaTags(); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_theme_color.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_theme_color.dart index 15f8fe863..b3cfcbfa2 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_theme_color.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_meta_theme_color.dart @@ -33,6 +33,7 @@ void getMetaThemeColor() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; expect(await controller.getMetaThemeColor(), isNotNull); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_original_url.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_original_url.dart index 635e95d91..182a4d1d4 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_original_url.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_original_url.dart @@ -33,6 +33,7 @@ void getOriginalUrl() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; var originUrl = (await controller.getOriginalUrl())?.toString(); expect(originUrl, url.toString()); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/get_title.dart b/flutter_inappwebview/example/integration_test/in_app_webview/get_title.dart index dcd492244..78de31c0e 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/get_title.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/get_title.dart @@ -50,6 +50,7 @@ void getTitle() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageStarted.future; await pageLoaded.future; diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/initial_url_request.dart b/flutter_inappwebview/example/integration_test/in_app_webview/initial_url_request.dart index 792467b3c..bb93e4bbe 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/initial_url_request.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/initial_url_request.dart @@ -80,6 +80,8 @@ void initialUrlRequest() { ), ), ); + // Platform view creation happens asynchronously. + await tester.pump(); await pageLoaded.future; final InAppWebViewController controller = diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/inject_css.dart b/flutter_inappwebview/example/integration_test/in_app_webview/inject_css.dart index cb5563941..108a0bdc2 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/inject_css.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/inject_css.dart @@ -35,6 +35,7 @@ void injectCSS() { final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.injectCSSCode(source: """ @@ -74,6 +75,7 @@ void injectCSS() { final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.injectCSSFileFromUrl( @@ -110,6 +112,7 @@ void injectCSS() { final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.injectCSSFileFromAsset( diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/inject_javascript_file.dart b/flutter_inappwebview/example/integration_test/in_app_webview/inject_javascript_file.dart index 1f60b5c02..b85c28790 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/inject_javascript_file.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/inject_javascript_file.dart @@ -37,6 +37,7 @@ void injectJavascriptFile() { final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.injectJavascriptFileFromUrl( @@ -97,6 +98,7 @@ void injectJavascriptFile() { final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.injectJavascriptFileFromAsset( diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/is_secure_context.dart b/flutter_inappwebview/example/integration_test/in_app_webview/is_secure_context.dart index 3c5d8a6cc..8457aff11 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/is_secure_context.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/is_secure_context.dart @@ -34,6 +34,7 @@ void isSecureContext() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoads.stream.first; expect(await controller.isSecureContext(), true); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/javascript_code_evaluation.dart b/flutter_inappwebview/example/integration_test/in_app_webview/javascript_code_evaluation.dart index 8f9a683f2..4ac6f3a6f 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/javascript_code_evaluation.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/javascript_code_evaluation.dart @@ -40,6 +40,7 @@ void javascriptCodeEvaluation() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; var result = await controller.evaluateJavascript(source: """ diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/load_data.dart b/flutter_inappwebview/example/integration_test/in_app_webview/load_data.dart index e5fb32ee9..4ada282aa 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/load_data.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/load_data.dart @@ -32,6 +32,8 @@ void loadData() { ); final InAppWebViewController controller = await controllerCompleter.future; + // do not wait for pump to not miss the load event + tester.pump(); await pageLoads.stream.first; final data = """ diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/load_file.dart b/flutter_inappwebview/example/integration_test/in_app_webview/load_file.dart index 09dbbcc85..df857e569 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/load_file.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/load_file.dart @@ -32,6 +32,8 @@ void loadFile() { ); final InAppWebViewController controller = await controllerCompleter.future; + // do not wait for pump to not miss the load event + tester.pump(); await pageLoads.stream.first; await controller.loadFile( diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/load_url.dart b/flutter_inappwebview/example/integration_test/in_app_webview/load_url.dart index bdb1f5bea..4fca15158 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/load_url.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/load_url.dart @@ -39,6 +39,7 @@ void loadUrl() { ), ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); expect(await firstUrlLoad.future, initialUrl.toString()); await controller.loadUrl( diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/main.dart b/flutter_inappwebview/example/integration_test/in_app_webview/main.dart index 6f9239dbb..4f2b9a863 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/main.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/main.dart @@ -220,7 +220,7 @@ void main() { contentBlocker(); httpAuthCredentialDatabase(); onConsoleMessage(); - onDownloadStartRequest(); + onDownloadStarting(); javascriptDialogs(); onReceivedHttpError(); onLoadResourceWithCustomScheme(); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_console_message.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_console_message.dart index 495d12c4b..a09ceb3ff 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_console_message.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_console_message.dart @@ -33,7 +33,7 @@ void onConsoleMessage() { ), ), ); - + await tester.pump(); final ConsoleMessage consoleMessage = await onConsoleMessageCompleter.future; expect(consoleMessage.message, 'message'); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart index ba735534d..952c0d0e7 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart @@ -1,6 +1,6 @@ part of 'main.dart'; -void onDownloadStartRequest() { +void onDownloadStarting() { final shouldSkip = kIsWeb ? true : ![ @@ -9,7 +9,7 @@ void onDownloadStartRequest() { TargetPlatform.macOS, ].contains(defaultTargetPlatform); - skippableTestWidgets('onDownloadStartRequest', (WidgetTester tester) async { + skippableTestWidgets('onDownloadStarting', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer onDownloadStartCompleter = Completer(); @@ -41,8 +41,9 @@ void onDownloadStartRequest() { onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, - onDownloadStartRequest: (controller, request) { + onDownloadStarting: (controller, request) { onDownloadStartCompleter.complete(request.url.toString()); + return null; }, ), ), diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_scroll_changed.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_scroll_changed.dart index 6903cbc89..7d4fe106d 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_scroll_changed.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_scroll_changed.dart @@ -38,9 +38,8 @@ void onScrollChanged() { ); final InAppWebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - await tester.pump(); + await pageLoaded.future; controller.scrollTo(x: 0, y: 500); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_title_changed.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_title_changed.dart index d0f6fb5a2..cbcd342a9 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_title_changed.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_title_changed.dart @@ -41,8 +41,8 @@ void onTitleChanged() { ); final InAppWebViewController controller = await controllerCompleter.future; - await pageLoaded.future; await tester.pump(); + await pageLoaded.future; await controller.evaluateJavascript( source: "document.title = 'title test';"); await expectLater(onTitleChangedCompleter.future, completes); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_update_visited_history.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_update_visited_history.dart index 9976e81e4..a43f1e4b9 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_update_visited_history.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_update_visited_history.dart @@ -43,6 +43,7 @@ void onUpdateVisitedHistory() { ); final InAppWebViewController controller = await controllerCompleter.future; + await tester.pump(); await pageLoaded.future; await controller.evaluateJavascript(source: """ diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_window_blur.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_window_blur.dart index cb7b3282f..0b1fe9c3d 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_window_blur.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_window_blur.dart @@ -29,6 +29,7 @@ void onWindowBlur() { ), ), ); + await tester.pump(); await expectLater(onWindowBlurCompleter.future, completes); }, skip: shouldSkip); } diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_window_focus.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_window_focus.dart index c07fe5282..ded782e10 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_window_focus.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_window_focus.dart @@ -29,6 +29,7 @@ void onWindowFocus() { ), ), ); + await tester.pump(); await expectLater(onWindowFocusCompleter.future, completes); }, skip: shouldSkip); } diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/print_current_page.dart b/flutter_inappwebview/example/integration_test/in_app_webview/print_current_page.dart index 13a11c0fd..d38e206cb 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/print_current_page.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/print_current_page.dart @@ -33,8 +33,8 @@ void printCurrentPage() { ); final InAppWebViewController controller = await controllerCompleter.future; - await pageLoaded.future; await tester.pump(); + await pageLoaded.future; await expectLater(controller.printCurrentPage(), completes); }, skip: shouldSkip); } diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/programmatic_scroll.dart b/flutter_inappwebview/example/integration_test/in_app_webview/programmatic_scroll.dart index 640e7129d..582c808af 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/programmatic_scroll.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/programmatic_scroll.dart @@ -69,9 +69,8 @@ void programmaticScroll() { final InAppWebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - await tester.pump(); + await pageLoaded.future; await controller.scrollTo(x: 0, y: 0); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/reload.dart b/flutter_inappwebview/example/integration_test/in_app_webview/reload.dart index 5cdb218da..b01c7fa28 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/reload.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/reload.dart @@ -71,6 +71,8 @@ void reload() { ); final InAppWebViewController controller = await controllerCompleter.future; + // do not wait for pump to not miss the load event + tester.pump(); String? reloadUrl = await pageLoads.stream.first; expect(reloadUrl, url.toString()); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/set_get_settings.dart b/flutter_inappwebview/example/integration_test/in_app_webview/set_get_settings.dart index 2c5a6d0fa..5bea368ca 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/set_get_settings.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/set_get_settings.dart @@ -32,6 +32,8 @@ void setGetSettings() { ), ), ); + // Platform view creation happens asynchronously. + await tester.pumpAndSettle(); final InAppWebViewController controller = await controllerCompleter.future; await pageLoaded.future; diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart b/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart index 078218caf..933bfc428 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart @@ -32,10 +32,14 @@ void userScripts() { UserScript( source: "var bar = 2;", injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, + forMainFrameOnly: + defaultTargetPlatform != TargetPlatform.android, contentWorld: ContentWorld.DEFAULT_CLIENT), UserScript( source: "var bar2 = 12;", injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, + forMainFrameOnly: + defaultTargetPlatform != TargetPlatform.android, contentWorld: ContentWorld.world(name: "test")), ]), onWebViewCreated: (controller) { diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart b/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart index 914011ec4..36a25f410 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart @@ -60,7 +60,9 @@ void webArchive() { controllerCompleter.complete(controller); }, onLoadStop: (controller, url) { - pageLoaded.complete(); + if (!pageLoaded.isCompleted) { + pageLoaded.complete(); + } }, ), ), diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/webview_windows.dart b/flutter_inappwebview/example/integration_test/in_app_webview/webview_windows.dart index 1db16a354..cc6eb3338 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/webview_windows.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/webview_windows.dart @@ -49,7 +49,7 @@ void webViewWindows() { ), ), ); - + await tester.pump(); await expectLater(pageLoaded.future, completes); }, skip: shouldSkipTest1); diff --git a/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart b/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart index 349902e6f..8a8a4392c 100644 --- a/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart +++ b/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart @@ -5,6 +5,8 @@ void clearAndSetProxyOverride() { ? true : ![ TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, ].contains(defaultTargetPlatform); skippableTestWidgets('clear and set proxy override', @@ -13,7 +15,7 @@ void clearAndSetProxyOverride() { Completer(); final Completer pageLoaded = Completer(); - var proxyAvailable = + var proxyAvailable = defaultTargetPlatform != TargetPlatform.android || await WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE); if (proxyAvailable) { diff --git a/flutter_inappwebview/example/ios/Flutter/AppFrameworkInfo.plist b/flutter_inappwebview/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105d..7c5696400 100755 --- a/flutter_inappwebview/example/ios/Flutter/AppFrameworkInfo.plist +++ b/flutter_inappwebview/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/flutter_inappwebview/example/ios/Podfile b/flutter_inappwebview/example/ios/Podfile index 88359b225..2c068c404 100644 --- a/flutter_inappwebview/example/ios/Podfile +++ b/flutter_inappwebview/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj b/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj index b2616bdee..2e0efe407 100644 --- a/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -212,6 +212,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, DFFD8453B8E169BF6BE74B49 /* [CP] Embed Pods Frameworks */, + 1F85D10872C1EEC5B3816B3D /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -231,7 +232,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1400; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 6143FF34290959650014A1FC = { @@ -294,6 +295,24 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1F85D10872C1EEC5B3816B3D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/permission_handler_apple/permission_handler_apple_privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/permission_handler_apple_privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -359,6 +378,7 @@ "${BUILT_PRODUCTS_DIR}/flutter_inappwebview_ios/flutter_inappwebview_ios.framework", "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", + "${BUILT_PRODUCTS_DIR}/pointer_interceptor_ios/pointer_interceptor_ios.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -368,6 +388,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_inappwebview_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pointer_interceptor_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -460,7 +481,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example.test"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4.test"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -497,7 +518,7 @@ ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example.test"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4.test"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -646,7 +667,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -681,7 +702,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/flutter_inappwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter_inappwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9c585c83b..cdef6845d 100755 --- a/flutter_inappwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter_inappwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ ? initialUserScripts, PullToRefreshController? pullToRefreshController}) : super( - windowId: windowId, - initialUserScripts: initialUserScripts, - pullToRefreshController: pullToRefreshController); + windowId: windowId, + initialUserScripts: initialUserScripts, + pullToRefreshController: pullToRefreshController, + webViewEnvironment: webViewEnvironment, + ); @override - Future onBrowserCreated() async { + void onBrowserCreated() { print("\n\nBrowser Created!\n\n"); } @override - Future onLoadStart(url) async {} + void onLoadStart(url) {} @override - Future onLoadStop(url) async { + void onLoadStop(url) { pullToRefreshController?.endRefreshing(); } @override - Future onPermissionRequest(request) async { + FutureOr onPermissionRequest(request) { return PermissionResponse( resources: request.resources, action: PermissionResponseAction.GRANT); } @@ -56,8 +58,8 @@ class MyInAppBrowser extends InAppBrowser { } @override - Future shouldOverrideUrlLoading( - navigationAction) async { + FutureOr shouldOverrideUrlLoading( + navigationAction) { print("\n\nOverride ${navigationAction.request.url}\n\n"); return NavigationActionPolicy.ALLOW; } diff --git a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart index 79dc016fa..4acc6d966 100755 --- a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart +++ b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart @@ -1,4 +1,5 @@ import 'dart:collection'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -73,8 +74,7 @@ class _InAppWebViewExampleScreenState extends State { onRefresh: () async { if (defaultTargetPlatform == TargetPlatform.android) { webViewController?.reload(); - } else if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.macOS) { + } else if (defaultTargetPlatform == TargetPlatform.iOS) { webViewController?.loadUrl( urlRequest: URLRequest(url: await webViewController?.getUrl())); @@ -115,6 +115,7 @@ class _InAppWebViewExampleScreenState extends State { children: [ InAppWebView( key: webViewKey, + webViewEnvironment: webViewEnvironment, initialUrlRequest: URLRequest(url: WebUri('https://flutter.dev')), // initialUrlRequest: @@ -127,13 +128,13 @@ class _InAppWebViewExampleScreenState extends State { onWebViewCreated: (controller) async { webViewController = controller; }, - onLoadStart: (controller, url) async { + onLoadStart: (controller, url) { setState(() { this.url = url.toString(); urlController.text = this.url; }); }, - onPermissionRequest: (controller, request) async { + onPermissionRequest: (controller, request) { return PermissionResponse( resources: request.resources, action: PermissionResponseAction.GRANT); @@ -163,7 +164,7 @@ class _InAppWebViewExampleScreenState extends State { return NavigationActionPolicy.ALLOW; }, - onLoadStop: (controller, url) async { + onLoadStop: (controller, url) { pullToRefreshController?.endRefreshing(); setState(() { this.url = url.toString(); diff --git a/flutter_inappwebview/example/lib/main.dart b/flutter_inappwebview/example/lib/main.dart index 21760f269..0a7802bd8 100755 --- a/flutter_inappwebview/example/lib/main.dart +++ b/flutter_inappwebview/example/lib/main.dart @@ -3,11 +3,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - import 'package:flutter_inappwebview_example/chrome_safari_browser_example.screen.dart'; import 'package:flutter_inappwebview_example/headless_in_app_webview.screen.dart'; -import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart'; import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart'; +import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart'; import 'package:flutter_inappwebview_example/web_authentication_session_example.screen.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -15,6 +14,7 @@ import 'package:pointer_interceptor/pointer_interceptor.dart'; // import 'package:permission_handler/permission_handler.dart'; final localhostServer = InAppLocalhostServer(documentRoot: 'assets'); +WebViewEnvironment? webViewEnvironment; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -22,12 +22,28 @@ Future main() async { // await Permission.microphone.request(); // await Permission.storage.request(); - if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { - await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.windows) { + final availableVersion = await WebViewEnvironment.getAvailableVersion(); + assert(availableVersion != null, + 'Failed to find an installed WebView2 runtime or non-stable Microsoft Edge installation.'); + + webViewEnvironment = await WebViewEnvironment.create( + settings: WebViewEnvironmentSettings( + additionalBrowserArguments: kDebugMode + ? '--enable-features=msEdgeDevToolsWdpRemoteDebugging' + : null, + userDataFolder: 'custom_path', + )); + + webViewEnvironment?.onBrowserProcessExited = (detail) { + if (kDebugMode) { + print('Browser process exited with detail: $detail'); + } + }; } - if (!kIsWeb) { - await localhostServer.start(); + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { + await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); } runApp(MyApp()); @@ -77,24 +93,18 @@ PointerInterceptor myDrawer({required BuildContext context}) { ]; } else if (defaultTargetPlatform == TargetPlatform.macOS) { children = [ - // ListTile( - // title: Text('InAppWebView'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/'); - // }, - // ), - // ListTile( - // title: Text('InAppBrowser'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/InAppBrowser'); - // }, - // ), ListTile( - title: Text('InAppBrowser'), + title: Text('InAppWebView'), onTap: () { Navigator.pushReplacementNamed(context, '/'); }, ), + ListTile( + title: Text('InAppBrowser'), + onTap: () { + Navigator.pushReplacementNamed(context, '/InAppBrowser'); + }, + ), ListTile( title: Text('WebAuthenticationSession'), onTap: () { @@ -108,6 +118,28 @@ PointerInterceptor myDrawer({required BuildContext context}) { }, ), ]; + } else if (defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux) { + children = [ + ListTile( + title: Text('InAppWebView'), + onTap: () { + Navigator.pushReplacementNamed(context, '/'); + }, + ), + ListTile( + title: Text('InAppBrowser'), + onTap: () { + Navigator.pushReplacementNamed(context, '/InAppBrowser'); + }, + ), + ListTile( + title: Text('HeadlessInAppWebView'), + onTap: () { + Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView'); + }, + ), + ]; } return PointerInterceptor( child: Drawer( @@ -152,14 +184,21 @@ class _MyAppState extends State { } if (defaultTargetPlatform == TargetPlatform.macOS) { return MaterialApp(initialRoute: '/', routes: { - // '/': (context) => InAppWebViewExampleScreen(), - // '/InAppBrowser': (context) => InAppBrowserExampleScreen(), - '/': (context) => InAppBrowserExampleScreen(), + '/': (context) => InAppWebViewExampleScreen(), + '/InAppBrowser': (context) => InAppBrowserExampleScreen(), '/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(), '/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(), }); + } else if (defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux) { + return MaterialApp(initialRoute: '/', routes: { + '/': (context) => InAppWebViewExampleScreen(), + '/InAppBrowser': (context) => InAppBrowserExampleScreen(), + '/HeadlessInAppWebView': (context) => + HeadlessInAppWebViewExampleScreen(), + }); } return MaterialApp(initialRoute: '/', routes: { '/': (context) => InAppWebViewExampleScreen(), diff --git a/flutter_inappwebview/example/macos/Runner.xcodeproj/project.pbxproj b/flutter_inappwebview/example/macos/Runner.xcodeproj/project.pbxproj index 9ec203702..4df2c9849 100644 --- a/flutter_inappwebview/example/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter_inappwebview/example/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/flutter_inappwebview/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter_inappwebview/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8ff1edec0..05ad528d0 100644 --- a/flutter_inappwebview/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter_inappwebview/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { return true diff --git a/flutter_inappwebview/example/macos/Runner/Info.plist b/flutter_inappwebview/example/macos/Runner/Info.plist index 4789daa6a..0c93eff43 100644 --- a/flutter_inappwebview/example/macos/Runner/Info.plist +++ b/flutter_inappwebview/example/macos/Runner/Info.plist @@ -22,6 +22,13 @@ $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile diff --git a/flutter_inappwebview/example/pubspec.yaml b/flutter_inappwebview/example/pubspec.yaml index fa737046b..0efca4a07 100755 --- a/flutter_inappwebview/example/pubspec.yaml +++ b/flutter_inappwebview/example/pubspec.yaml @@ -12,8 +12,8 @@ version: 1.0.0+1 publish_to: none environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" dependencies: flutter: @@ -21,12 +21,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.4 - flutter_downloader: 1.10.2 - path_provider: 2.0.15 - permission_handler: 10.2.0 - url_launcher: 6.1.11 - pointer_interceptor: ^0.9.3+4 + cupertino_icons: ^1.0.8 + flutter_downloader: 1.11.8 + path_provider: 2.1.4 + permission_handler: 11.3.1 + url_launcher: 6.3.0 + pointer_interceptor: ^0.10.1+2 # mime: ^1.0.4 # connectivity: ^0.4.5+6 flutter_inappwebview: @@ -43,6 +43,8 @@ dependency_overrides: path: ../../flutter_inappwebview_macos flutter_inappwebview_web: path: ../../flutter_inappwebview_web + flutter_inappwebview_windows: + path: ../../flutter_inappwebview_windows dev_dependencies: flutter_test: @@ -56,7 +58,7 @@ dev_dependencies: # git: # url: https://github.com/flutter/plugins.git # path: packages/integration_test - flutter_lints: ^2.0.1 + flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/flutter_inappwebview/example/test/widget_test.dart b/flutter_inappwebview/example/test/widget_test.dart new file mode 100644 index 000000000..e69de29bb diff --git a/flutter_inappwebview/example/web/index.html b/flutter_inappwebview/example/web/index.html index cb50c1621..0e7eaf616 100644 --- a/flutter_inappwebview/example/web/index.html +++ b/flutter_inappwebview/example/web/index.html @@ -39,69 +39,6 @@ - + diff --git a/flutter_inappwebview/example/windows/.gitignore b/flutter_inappwebview/example/windows/.gitignore new file mode 100644 index 000000000..5d57396c8 --- /dev/null +++ b/flutter_inappwebview/example/windows/.gitignore @@ -0,0 +1,19 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +cmake-build-* \ No newline at end of file diff --git a/flutter_inappwebview/example/windows/CMakeLists.txt b/flutter_inappwebview/example/windows/CMakeLists.txt new file mode 100644 index 000000000..d960948af --- /dev/null +++ b/flutter_inappwebview/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example 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 "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + 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() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# 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_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +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) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +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. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter_inappwebview/example/windows/flutter/CMakeLists.txt b/flutter_inappwebview/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..903f4899d --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +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. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# 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/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app 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. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..031b86957 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.h b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/flutter_inappwebview/example/windows/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 RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake b/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..997d0b803 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows + permission_handler_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows 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}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter_inappwebview/example/windows/runner/CMakeLists.txt b/flutter_inappwebview/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +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} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# 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 build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter_inappwebview/example/windows/runner/Runner.rc b/flutter_inappwebview/example/windows/runner/Runner.rc new file mode 100644 index 000000000..302ceb4ba --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.pichillilorenzo" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.pichillilorenzo. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter_inappwebview/example/windows/runner/flutter_window.cpp b/flutter_inappwebview/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..955ee3038 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter_inappwebview/example/windows/runner/flutter_window.h b/flutter_inappwebview/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter_inappwebview/example/windows/runner/main.cpp b/flutter_inappwebview/example/windows/runner/main.cpp new file mode 100644 index 000000000..a61bf80d3 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter_inappwebview/example/windows/runner/resource.h b/flutter_inappwebview/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter_inappwebview/example/windows/runner/resources/app_icon.ico b/flutter_inappwebview/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000..c04e20caf Binary files /dev/null and b/flutter_inappwebview/example/windows/runner/resources/app_icon.ico differ diff --git a/flutter_inappwebview/example/windows/runner/runner.exe.manifest b/flutter_inappwebview/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..a42ea7687 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter_inappwebview/example/windows/runner/utils.cpp b/flutter_inappwebview/example/windows/runner/utils.cpp new file mode 100644 index 000000000..b2b08734d --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter_inappwebview/example/windows/runner/utils.h b/flutter_inappwebview/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter_inappwebview/example/windows/runner/win32_window.cpp b/flutter_inappwebview/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..786879fac --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/win32_window.cpp @@ -0,0 +1,309 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + + /// Window attribute that enables dark mode window decorations. + /// + /// Redefined in case the developer's machine has a Windows SDK older than + /// version 10.0.22000.0. + /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + + /// Registry key for app theme preference. + /// + /// A value of 0 indicates apps should use dark mode. A non-zero or missing + /// value indicates apps should use light mode. + constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + + // The number of Win32Window objects that currently exist. + static int g_active_window_count = 0; + + using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + + // Scale helper to convert logical scaler values to physical using passed in + // scale factor + int Scale(int source, double scale_factor) + { + return static_cast(source * scale_factor); + } + + // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. + // This API is only needed for PerMonitor V1 awareness mode. + void EnableFullDpiSupportIfAvailable(HWND hwnd) + { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); + } + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { +public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() + { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + +private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() +{ + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() +{ + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() +{ + ++g_active_window_count; +} + +Win32Window::~Win32Window() +{ + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) +{ + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = { static_cast(origin.x), + static_cast(origin.y) }; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() +{ + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept +{ + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } + else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept +{ + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() +{ + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept +{ + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) +{ + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() +{ + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() +{ + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) +{ + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() +{ + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() +{ + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) +{ + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter_inappwebview/example/windows/runner/win32_window.h b/flutter_inappwebview/example/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter_inappwebview/lib/src/cookie_manager.dart b/flutter_inappwebview/lib/src/cookie_manager.dart index 19afa2777..bebf0f303 100755 --- a/flutter_inappwebview/lib/src/cookie_manager.dart +++ b/flutter_inappwebview/lib/src/cookie_manager.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import 'in_app_webview/in_app_webview_controller.dart'; +import 'webview_environment/webview_environment.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager} class CookieManager { @@ -31,12 +32,22 @@ class CookieManager { static CookieManager? _instance; + WebViewEnvironment? _webViewEnvironment; + ///Gets the [CookieManager] shared instance. - static CookieManager instance() { - if (_instance == null) { - _instance = CookieManager(); + /// + ///[webViewEnvironment] (Supported only on Windows) - Used to create the [CookieManager] using the specified environment. + static CookieManager instance({WebViewEnvironment? webViewEnvironment}) { + if (webViewEnvironment == null) { + if (_instance == null) { + _instance = CookieManager(); + } + return _instance!; + } else { + return CookieManager.fromPlatformCreationParams( + PlatformCookieManagerCreationParams( + webViewEnvironment: webViewEnvironment.platform)); } - return _instance!; } ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.setCookie} @@ -132,6 +143,9 @@ class CookieManager { ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.removeSessionCookies} Future removeSessionCookies() => platform.removeSessionCookies(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.flush} + Future flush() => platform.flush(); } ///Class that contains only iOS-specific methods of [CookieManager]. diff --git a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart index 3393cafd6..dbaeb6d63 100755 --- a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart +++ b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart @@ -11,25 +11,29 @@ import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/main.dart'; import '../in_app_webview/in_app_webview_controller.dart'; +import '../webview_environment/webview_environment.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} class InAppBrowser implements PlatformInAppBrowserEvents { /// Constructs a [InAppBrowser]. /// /// {@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} - InAppBrowser( - {ContextMenu? contextMenu, - PullToRefreshController? pullToRefreshController, - FindInteractionController? findInteractionController, - UnmodifiableListView? initialUserScripts, - int? windowId}) - : this.fromPlatformCreationParams( + InAppBrowser({ + ContextMenu? contextMenu, + PullToRefreshController? pullToRefreshController, + FindInteractionController? findInteractionController, + UnmodifiableListView? initialUserScripts, + int? windowId, + WebViewEnvironment? webViewEnvironment, + }) : this.fromPlatformCreationParams( PlatformInAppBrowserCreationParams( - contextMenu: contextMenu, - pullToRefreshController: pullToRefreshController?.platform, - findInteractionController: findInteractionController?.platform, - initialUserScripts: initialUserScripts, - windowId: windowId), + contextMenu: contextMenu, + pullToRefreshController: pullToRefreshController?.platform, + findInteractionController: findInteractionController?.platform, + initialUserScripts: initialUserScripts, + windowId: windowId, + webViewEnvironment: webViewEnvironment?.platform, + ), ); /// Constructs a [InAppBrowser] from creation params for a specific @@ -195,96 +199,133 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @mustCallSuper void dispose() => platform.dispose(); + ///Use [onFormResubmission] instead. + @Deprecated('Use onFormResubmission instead') @override - Future? androidOnFormResubmission(Uri? url) { + FutureOr? androidOnFormResubmission(Uri? url) { return null; } + ///Use [onGeolocationPermissionsHidePrompt] instead. + @Deprecated("Use onGeolocationPermissionsHidePrompt instead") @override void androidOnGeolocationPermissionsHidePrompt() {} + ///Use [onGeolocationPermissionsShowPrompt] instead. + @Deprecated("Use onGeolocationPermissionsShowPrompt instead") @override - Future? + FutureOr? androidOnGeolocationPermissionsShowPrompt(String origin) { return null; } + ///Use [onJsBeforeUnload] instead. + @Deprecated('Use onJsBeforeUnload instead') @override - Future? androidOnJsBeforeUnload( + FutureOr? androidOnJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } + ///Use [onPermissionRequest] instead. + @Deprecated("Use onPermissionRequest instead") @override - Future? androidOnPermissionRequest( + FutureOr? androidOnPermissionRequest( String origin, List resources) { return null; } + ///Use [onReceivedIcon] instead. + @Deprecated('Use onReceivedIcon instead') @override void androidOnReceivedIcon(Uint8List icon) {} + ///Use [onReceivedLoginRequest] instead. + @Deprecated('Use onReceivedLoginRequest instead') @override void androidOnReceivedLoginRequest(LoginRequest loginRequest) {} + ///Use [onReceivedTouchIconUrl] instead. + @Deprecated('Use onReceivedTouchIconUrl instead') @override void androidOnReceivedTouchIconUrl(Uri url, bool precomposed) {} + ///Use [onRenderProcessGone] instead. + @Deprecated("Use onRenderProcessGone instead") @override void androidOnRenderProcessGone(RenderProcessGoneDetail detail) {} + ///Use [onRenderProcessResponsive] instead. + @Deprecated("Use onRenderProcessResponsive instead") @override - Future? androidOnRenderProcessResponsive( + FutureOr? androidOnRenderProcessResponsive( Uri? url) { return null; } + ///Use [onRenderProcessUnresponsive] instead. + @Deprecated("Use onRenderProcessUnresponsive instead") @override - Future? androidOnRenderProcessUnresponsive( + FutureOr? androidOnRenderProcessUnresponsive( Uri? url) { return null; } + ///Use [onSafeBrowsingHit] instead. + @Deprecated("Use onSafeBrowsingHit instead") @override - Future? androidOnSafeBrowsingHit( + FutureOr? androidOnSafeBrowsingHit( Uri url, SafeBrowsingThreat? threatType) { return null; } + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use onZoomScaleChanged instead') @override void androidOnScaleChanged(double oldScale, double newScale) {} + ///Use [shouldInterceptRequest] instead. + @Deprecated("Use shouldInterceptRequest instead") @override - Future? androidShouldInterceptRequest( + FutureOr? androidShouldInterceptRequest( WebResourceRequest request) { return null; } + ///Use [onDidReceiveServerRedirectForProvisionalNavigation] instead. + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') @override void iosOnDidReceiveServerRedirectForProvisionalNavigation() {} + ///Use [onNavigationResponse] instead. + @Deprecated('Use onNavigationResponse instead') @override - Future? iosOnNavigationResponse( + FutureOr? iosOnNavigationResponse( IOSWKNavigationResponse navigationResponse) { return null; } + ///Use [onWebContentProcessDidTerminate] instead. + @Deprecated('Use onWebContentProcessDidTerminate instead') @override void iosOnWebContentProcessDidTerminate() {} + ///Use [shouldAllowDeprecatedTLS] instead. + @Deprecated('Use shouldAllowDeprecatedTLS instead') @override - Future? iosShouldAllowDeprecatedTLS( + FutureOr? iosShouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @override - Future? onAjaxProgress(AjaxRequest ajaxRequest) { + FutureOr? onAjaxProgress(AjaxRequest ajaxRequest) { return null; } @override - Future? onAjaxReadyStateChange(AjaxRequest ajaxRequest) { + FutureOr? onAjaxReadyStateChange( + AjaxRequest ajaxRequest) { return null; } @@ -305,19 +346,29 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} @override - Future? onCreateWindow(CreateWindowAction createWindowAction) { + FutureOr? onCreateWindow(CreateWindowAction createWindowAction) { return null; } @override void onDidReceiveServerRedirectForProvisionalNavigation() {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') @override void onDownloadStart(Uri url) {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') @override void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + @override + FutureOr? onDownloadStarting( + DownloadStartRequest downloadStartRequest) { + return null; + } + @override void onEnterFullscreen() {} @@ -327,12 +378,14 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @override void onExitFullscreen() {} + ///Use [FindInteractionController.onFindResultReceived] instead. + @Deprecated('Use FindInteractionController.onFindResultReceived instead') @override void onFindResultReceived( int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting) {} @override - Future? onFormResubmission(WebUri? url) { + FutureOr? onFormResubmission(WebUri? url) { return null; } @@ -340,48 +393,54 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onGeolocationPermissionsHidePrompt() {} @override - Future? + FutureOr? onGeolocationPermissionsShowPrompt(String origin) { return null; } @override - Future? onJsAlert(JsAlertRequest jsAlertRequest) { + FutureOr? onJsAlert(JsAlertRequest jsAlertRequest) { return null; } @override - Future? onJsBeforeUnload( + FutureOr? onJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @override - Future? onJsConfirm(JsConfirmRequest jsConfirmRequest) { + FutureOr? onJsConfirm(JsConfirmRequest jsConfirmRequest) { return null; } @override - Future? onJsPrompt(JsPromptRequest jsPromptRequest) { + FutureOr? onJsPrompt(JsPromptRequest jsPromptRequest) { return null; } + ///Use [onReceivedError] instead. + @Deprecated("Use onReceivedError instead") @override void onLoadError(Uri? url, int code, String message) {} + ///Use [onReceivedHttpError] instead. + @Deprecated("Use onReceivedHttpError instead") @override void onLoadHttpError(Uri? url, int statusCode, String description) {} @override void onLoadResource(LoadedResource resource) {} + ///Use [onLoadResourceWithCustomScheme] instead. + @Deprecated('Use onLoadResourceWithCustomScheme instead') @override - Future? onLoadResourceCustomScheme(Uri url) { + FutureOr? onLoadResourceCustomScheme(Uri url) { return null; } @override - Future? onLoadResourceWithCustomScheme( + FutureOr? onLoadResourceWithCustomScheme( WebResourceRequest request) { return null; } @@ -400,7 +459,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { MediaCaptureState? oldState, MediaCaptureState? newState) {} @override - Future? onNavigationResponse( + FutureOr? onNavigationResponse( NavigationResponse navigationResponse) { return null; } @@ -412,7 +471,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onPageCommitVisible(WebUri? url) {} @override - Future? onPermissionRequest( + FutureOr? onPermissionRequest( PermissionRequest permissionRequest) { return null; } @@ -420,11 +479,13 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @override void onPermissionRequestCanceled(PermissionRequest permissionRequest) {} + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") @override void onPrint(Uri? url) {} @override - Future? onPrintRequest( + FutureOr? onPrintRequest( WebUri? url, PlatformPrintJobController? printJobController) { return null; } @@ -433,8 +494,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onProgressChanged(int progress) {} @override - Future? onReceivedClientCertRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedClientCertRequest( + ClientCertChallenge challenge) { return null; } @@ -442,8 +503,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onReceivedError(WebResourceRequest request, WebResourceError error) {} @override - Future? onReceivedHttpAuthRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedHttpAuthRequest( + HttpAuthenticationChallenge challenge) { return null; } @@ -458,8 +519,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onReceivedLoginRequest(LoginRequest loginRequest) {} @override - Future? onReceivedServerTrustAuthRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedServerTrustAuthRequest( + ServerTrustChallenge challenge) { return null; } @@ -470,12 +531,13 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onRenderProcessGone(RenderProcessGoneDetail detail) {} @override - Future? onRenderProcessResponsive(WebUri? url) { + FutureOr? onRenderProcessResponsive( + WebUri? url) { return null; } @override - Future? onRenderProcessUnresponsive( + FutureOr? onRenderProcessUnresponsive( WebUri? url) { return null; } @@ -484,7 +546,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onRequestFocus() {} @override - Future? onSafeBrowsingHit( + FutureOr? onSafeBrowsingHit( WebUri url, SafeBrowsingThreat? threatType) { return null; } @@ -511,34 +573,45 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onZoomScaleChanged(double oldScale, double newScale) {} @override - Future? shouldAllowDeprecatedTLS( + FutureOr? shouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @override - Future? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { + FutureOr? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { return null; } @override - Future? shouldInterceptFetchRequest( + FutureOr? shouldInterceptFetchRequest( FetchRequest fetchRequest) { return null; } @override - Future? shouldInterceptRequest( + FutureOr? shouldInterceptRequest( WebResourceRequest request) { return null; } @override - Future? shouldOverrideUrlLoading( + FutureOr? shouldOverrideUrlLoading( NavigationAction navigationAction) { return null; } @override void onMainWindowWillClose() {} + + @override + void onProcessFailed(ProcessFailedDetail detail) {} + + @override + void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + @override + FutureOr onShowFileChooser(ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview/lib/src/in_app_localhost_server.dart b/flutter_inappwebview/lib/src/in_app_localhost_server.dart index daae11b7b..841f0cc29 100755 --- a/flutter_inappwebview/lib/src/in_app_localhost_server.dart +++ b/flutter_inappwebview/lib/src/in_app_localhost_server.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer} @@ -9,12 +10,14 @@ class InAppLocalhostServer { String directoryIndex = 'index.html', String documentRoot = './', bool shared = false, + Future Function(HttpRequest request)? onData, }) : this.fromPlatformCreationParams( PlatformInAppLocalhostServerCreationParams( port: port, directoryIndex: directoryIndex, documentRoot: documentRoot, - shared: shared), + shared: shared, + onData: onData), ); /// Constructs a [InAppLocalhostServer] from creation params for a specific @@ -42,6 +45,9 @@ class InAppLocalhostServer { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.shared} bool get shared => platform.shared; + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.onData} + Future Function(HttpRequest request)? get onData => platform.onData; + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.start} Future start() => platform.start(); diff --git a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart index 5f714d677..252f3e5f8 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,12 +1,13 @@ +import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui'; 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_controller.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'in_app_webview_controller.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} class HeadlessInAppWebView { @@ -36,263 +37,139 @@ class HeadlessInAppWebView { platform: webViewControllerPlatform); } - HeadlessInAppWebView({ - Size initialSize = const Size(-1, -1), - int? windowId, - HeadlessInAppWebView? headlessWebView, - InAppWebViewKeepAlive? keepAlive, - bool? preventGestureDelay, - @Deprecated('Use onGeolocationPermissionsHidePrompt instead') - void Function(InAppWebViewController controller)? - androidOnGeolocationPermissionsHidePrompt, - @Deprecated('Use onGeolocationPermissionsShowPrompt instead') - Future Function( - InAppWebViewController controller, String origin)? - androidOnGeolocationPermissionsShowPrompt, - @Deprecated('Use onPermissionRequest instead') - Future Function( - InAppWebViewController controller, - String origin, - List resources)? - androidOnPermissionRequest, - @Deprecated('Use onSafeBrowsingHit instead') - Future Function(InAppWebViewController controller, - Uri url, SafeBrowsingThreat? threatType)? - androidOnSafeBrowsingHit, - InAppWebViewInitialData? initialData, - String? initialFile, - @Deprecated('Use initialSettings instead') - InAppWebViewGroupOptions? initialOptions, - InAppWebViewSettings? initialSettings, - URLRequest? initialUrlRequest, - UnmodifiableListView? initialUserScripts, - PullToRefreshController? pullToRefreshController, - FindInteractionController? findInteractionController, - ContextMenu? contextMenu, - void Function(InAppWebViewController controller, WebUri? url)? - onPageCommitVisible, - void Function(InAppWebViewController controller, String? title)? - onTitleChanged, - @Deprecated( - 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') - void Function(InAppWebViewController controller)? - iosOnDidReceiveServerRedirectForProvisionalNavigation, - @Deprecated('Use onWebContentProcessDidTerminate instead') - void Function(InAppWebViewController controller)? - iosOnWebContentProcessDidTerminate, - @Deprecated('Use onNavigationResponse instead') - Future Function( - InAppWebViewController controller, - IOSWKNavigationResponse navigationResponse)? - iosOnNavigationResponse, - @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - iosShouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxProgress, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxReadyStateChange, - void Function( - InAppWebViewController controller, ConsoleMessage consoleMessage)? - onConsoleMessage, - Future Function(InAppWebViewController controller, - CreateWindowAction createWindowAction)? - onCreateWindow, - void Function(InAppWebViewController controller)? onCloseWindow, - void Function(InAppWebViewController controller)? onWindowFocus, - void Function(InAppWebViewController controller)? onWindowBlur, - @Deprecated('Use onReceivedIcon instead') - void Function(InAppWebViewController controller, Uint8List icon)? - androidOnReceivedIcon, - @Deprecated('Use onReceivedTouchIconUrl instead') - void Function(InAppWebViewController controller, Uri url, bool precomposed)? - androidOnReceivedTouchIconUrl, - @Deprecated('Use onDownloadStartRequest instead') - void Function(InAppWebViewController controller, Uri url)? onDownloadStart, - void Function(InAppWebViewController controller, - DownloadStartRequest downloadStartRequest)? - onDownloadStartRequest, - @Deprecated('Use FindInteractionController.onFindResultReceived instead') - void Function(InAppWebViewController controller, int activeMatchOrdinal, - int numberOfMatches, bool isDoneCounting)? - onFindResultReceived, - Future Function( - InAppWebViewController controller, JsAlertRequest jsAlertRequest)? - onJsAlert, - Future Function(InAppWebViewController controller, - JsConfirmRequest jsConfirmRequest)? - onJsConfirm, - Future Function( - InAppWebViewController controller, JsPromptRequest jsPromptRequest)? - onJsPrompt, - @Deprecated("Use onReceivedError instead") - void Function(InAppWebViewController controller, Uri? url, int code, - String message)? - onLoadError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceError error)? - onReceivedError, - @Deprecated("Use onReceivedHttpError instead") - void Function(InAppWebViewController controller, Uri? url, int statusCode, - String description)? - onLoadHttpError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceResponse errorResponse)? - onReceivedHttpError, - void Function(InAppWebViewController controller, LoadedResource resource)? - onLoadResource, - @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future Function( - InAppWebViewController controller, Uri url)? - onLoadResourceCustomScheme, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - onLoadResourceWithCustomScheme, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, - void Function(InAppWebViewController controller, - InAppWebViewHitTestResult hitTestResult)? - onLongPressHitTestResult, - @Deprecated("Use onPrintRequest instead") - void Function(InAppWebViewController controller, Uri? url)? onPrint, - Future Function(InAppWebViewController controller, WebUri? url, - PlatformPrintJobController? printJobController)? - onPrintRequest, - void Function(InAppWebViewController controller, int progress)? - onProgressChanged, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedClientCertRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedHttpAuthRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedServerTrustAuthRequest, - void Function(InAppWebViewController controller, int x, int y)? - onScrollChanged, - void Function( - InAppWebViewController controller, WebUri? url, bool? isReload)? - onUpdateVisitedHistory, - void Function(InAppWebViewController controller)? onWebViewCreated, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - shouldInterceptAjaxRequest, - Future Function( - InAppWebViewController controller, FetchRequest fetchRequest)? - shouldInterceptFetchRequest, - Future Function(InAppWebViewController controller, - NavigationAction navigationAction)? - shouldOverrideUrlLoading, - void Function(InAppWebViewController controller)? onEnterFullscreen, - void Function(InAppWebViewController controller)? onExitFullscreen, - void Function(InAppWebViewController controller, int x, int y, - bool clampedX, bool clampedY)? - onOverScrolled, - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - onZoomScaleChanged, - @Deprecated('Use shouldInterceptRequest instead') - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - androidShouldInterceptRequest, - @Deprecated('Use onRenderProcessUnresponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessUnresponsive, - @Deprecated('Use onRenderProcessResponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessResponsive, - @Deprecated('Use onRenderProcessGone instead') - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - androidOnRenderProcessGone, - @Deprecated('Use onFormResubmission instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnFormResubmission, - @Deprecated('Use onZoomScaleChanged instead') - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - androidOnScaleChanged, - @Deprecated('Use onJsBeforeUnload instead') - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - androidOnJsBeforeUnload, - @Deprecated('Use onReceivedLoginRequest instead') - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - androidOnReceivedLoginRequest, - void Function(InAppWebViewController controller)? - onDidReceiveServerRedirectForProvisionalNavigation, - Future Function( - InAppWebViewController controller, WebUri? url)? - onFormResubmission, - void Function(InAppWebViewController controller)? - onGeolocationPermissionsHidePrompt, - Future Function( - InAppWebViewController controller, String origin)? - onGeolocationPermissionsShowPrompt, - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - onJsBeforeUnload, - Future Function( - InAppWebViewController controller, - NavigationResponse navigationResponse)? - onNavigationResponse, - Future Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequest, - void Function(InAppWebViewController controller, Uint8List icon)? - onReceivedIcon, - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - onReceivedLoginRequest, - void Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequestCanceled, - void Function(InAppWebViewController controller)? onRequestFocus, - void Function( - InAppWebViewController controller, WebUri url, bool precomposed)? - onReceivedTouchIconUrl, - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - onRenderProcessGone, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessResponsive, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessUnresponsive, - Future Function(InAppWebViewController controller, - WebUri url, SafeBrowsingThreat? threatType)? - onSafeBrowsingHit, - void Function(InAppWebViewController controller)? - onWebContentProcessDidTerminate, - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - shouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - shouldInterceptRequest, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onCameraCaptureStateChanged, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onMicrophoneCaptureStateChanged, - void Function(InAppWebViewController controller, Size oldContentSize, - Size newContentSize)? - onContentSizeChanged, - }) : this.fromPlatformCreationParams( + HeadlessInAppWebView( + {Size initialSize = const Size(-1, -1), + int? windowId, + HeadlessInAppWebView? headlessWebView, + InAppWebViewKeepAlive? keepAlive, + bool? preventGestureDelay, + WebViewEnvironment? webViewEnvironment, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + void Function(InAppWebViewController controller)? + androidOnGeolocationPermissionsHidePrompt, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + FutureOr Function( + InAppWebViewController controller, String origin)? + androidOnGeolocationPermissionsShowPrompt, + @Deprecated('Use onPermissionRequest instead') + FutureOr Function( + InAppWebViewController controller, + String origin, + List resources)? + androidOnPermissionRequest, + @Deprecated('Use onSafeBrowsingHit instead') + FutureOr Function( + InAppWebViewController controller, + Uri url, + SafeBrowsingThreat? threatType)? + androidOnSafeBrowsingHit, + InAppWebViewInitialData? initialData, + String? initialFile, + @Deprecated('Use initialSettings instead') + InAppWebViewGroupOptions? initialOptions, + InAppWebViewSettings? initialSettings, + URLRequest? initialUrlRequest, + UnmodifiableListView? initialUserScripts, + PullToRefreshController? pullToRefreshController, + FindInteractionController? findInteractionController, + ContextMenu? contextMenu, + void Function(InAppWebViewController controller, WebUri? url)? + onPageCommitVisible, + void Function(InAppWebViewController controller, String? title)? + onTitleChanged, + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') + void Function(InAppWebViewController controller)? + iosOnDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onWebContentProcessDidTerminate instead') + void Function(InAppWebViewController controller)? + iosOnWebContentProcessDidTerminate, + @Deprecated('Use onNavigationResponse instead') + FutureOr Function(InAppWebViewController controller, IOSWKNavigationResponse navigationResponse)? iosOnNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? iosShouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxProgress, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxReadyStateChange, + void Function(InAppWebViewController controller, ConsoleMessage consoleMessage)? onConsoleMessage, + FutureOr Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow, + void Function(InAppWebViewController controller)? onCloseWindow, + void Function(InAppWebViewController controller)? onWindowFocus, + void Function(InAppWebViewController controller)? onWindowBlur, + @Deprecated('Use onReceivedIcon instead') void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') void Function(InAppWebViewController controller, Uri url, bool precomposed)? androidOnReceivedTouchIconUrl, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, Uri url)? onDownloadStart, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStartRequest, + FutureOr Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStarting, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') void Function(InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting)? onFindResultReceived, + FutureOr Function(InAppWebViewController controller, JsAlertRequest jsAlertRequest)? onJsAlert, + FutureOr Function(InAppWebViewController controller, JsConfirmRequest jsConfirmRequest)? onJsConfirm, + FutureOr Function(InAppWebViewController controller, JsPromptRequest jsPromptRequest)? onJsPrompt, + @Deprecated("Use onReceivedError instead") void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceError error)? onReceivedError, + @Deprecated("Use onReceivedHttpError instead") void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError, + void Function(InAppWebViewController controller, LoadedResource resource)? onLoadResource, + @Deprecated('Use onLoadResourceWithCustomScheme instead') FutureOr Function(InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, + void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult, + @Deprecated("Use onPrintRequest instead") void Function(InAppWebViewController controller, Uri? url)? onPrint, + FutureOr Function(InAppWebViewController controller, WebUri? url, PlatformPrintJobController? printJobController)? onPrintRequest, + void Function(InAppWebViewController controller, int progress)? onProgressChanged, + FutureOr Function(InAppWebViewController controller, ClientCertChallenge challenge)? onReceivedClientCertRequest, + FutureOr Function(InAppWebViewController controller, HttpAuthenticationChallenge challenge)? onReceivedHttpAuthRequest, + FutureOr Function(InAppWebViewController controller, ServerTrustChallenge challenge)? onReceivedServerTrustAuthRequest, + void Function(InAppWebViewController controller, int x, int y)? onScrollChanged, + void Function(InAppWebViewController controller, WebUri? url, bool? isReload)? onUpdateVisitedHistory, + void Function(InAppWebViewController controller)? onWebViewCreated, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? shouldInterceptAjaxRequest, + FutureOr Function(InAppWebViewController controller, FetchRequest fetchRequest)? shouldInterceptFetchRequest, + FutureOr Function(InAppWebViewController controller, NavigationAction navigationAction)? shouldOverrideUrlLoading, + void Function(InAppWebViewController controller)? onEnterFullscreen, + void Function(InAppWebViewController controller)? onExitFullscreen, + void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled, + void Function(InAppWebViewController controller, double oldScale, double newScale)? onZoomScaleChanged, + @Deprecated('Use shouldInterceptRequest instead') FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? androidShouldInterceptRequest, + @Deprecated('Use onRenderProcessUnresponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessUnresponsive, + @Deprecated('Use onRenderProcessResponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessResponsive, + @Deprecated('Use onRenderProcessGone instead') void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? androidOnRenderProcessGone, + @Deprecated('Use onFormResubmission instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') void Function(InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged, + @Deprecated('Use onJsBeforeUnload instead') FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? androidOnJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') void Function(InAppWebViewController controller, LoginRequest loginRequest)? androidOnReceivedLoginRequest, + void Function(InAppWebViewController controller)? onDidReceiveServerRedirectForProvisionalNavigation, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onFormResubmission, + void Function(InAppWebViewController controller)? onGeolocationPermissionsHidePrompt, + FutureOr Function(InAppWebViewController controller, String origin)? onGeolocationPermissionsShowPrompt, + FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? onJsBeforeUnload, + FutureOr Function(InAppWebViewController controller, NavigationResponse navigationResponse)? onNavigationResponse, + FutureOr Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest, + void Function(InAppWebViewController controller, Uint8List icon)? onReceivedIcon, + void Function(InAppWebViewController controller, LoginRequest loginRequest)? onReceivedLoginRequest, + void Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequestCanceled, + void Function(InAppWebViewController controller)? onRequestFocus, + void Function(InAppWebViewController controller, WebUri url, bool precomposed)? onReceivedTouchIconUrl, + void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? onRenderProcessGone, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessResponsive, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessUnresponsive, + FutureOr Function(InAppWebViewController controller, WebUri url, SafeBrowsingThreat? threatType)? onSafeBrowsingHit, + void Function(InAppWebViewController controller)? onWebContentProcessDidTerminate, + FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? shouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged, + void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, + void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) + : this.fromPlatformCreationParams( params: PlatformHeadlessInAppWebViewCreationParams( controllerFromPlatform: (PlatformInAppWebViewController controller) => InAppWebViewController.fromPlatform(platform: controller), @@ -307,6 +184,7 @@ class HeadlessInAppWebView { pullToRefreshController: pullToRefreshController?.platform, findInteractionController: findInteractionController?.platform, contextMenu: contextMenu, + webViewEnvironment: webViewEnvironment?.platform, onWebViewCreated: onWebViewCreated != null ? (controller) => onWebViewCreated.call(controller) : null, @@ -358,6 +236,10 @@ class HeadlessInAppWebView { ? (controller, downloadStartRequest) => onDownloadStartRequest.call(controller, downloadStartRequest) : null, + onDownloadStarting: onDownloadStarting != null + ? (controller, downloadStartRequest) => + onDownloadStarting.call(controller, downloadStartRequest) + : null, onLoadResourceCustomScheme: onLoadResourceCustomScheme != null ? (controller, url) => onLoadResourceCustomScheme.call(controller, url) @@ -633,6 +515,17 @@ class HeadlessInAppWebView { onContentSizeChanged.call( controller, oldContentSize, newContentSize) : null, + onProcessFailed: onProcessFailed != null + ? (controller, detail) => onProcessFailed.call(controller, detail) + : null, + onAcceleratorKeyPressed: onAcceleratorKeyPressed != null + ? (controller, detail) => + onAcceleratorKeyPressed.call(controller, detail) + : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, )); ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView.run} diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart index e2490bfc3..6368630e7 100755 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart @@ -1,19 +1,19 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; -import 'in_app_webview_controller.dart'; + import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} class InAppWebView extends StatefulWidget { @@ -34,265 +34,141 @@ class InAppWebView extends StatefulWidget { final PlatformInAppWebViewWidget platform; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} - InAppWebView({ - Key? key, - Set>? gestureRecognizers, - int? windowId, - HeadlessInAppWebView? headlessWebView, - InAppWebViewKeepAlive? keepAlive, - bool? preventGestureDelay, - TextDirection? layoutDirection, - @Deprecated('Use onGeolocationPermissionsHidePrompt instead') - void Function(InAppWebViewController controller)? - androidOnGeolocationPermissionsHidePrompt, - @Deprecated('Use onGeolocationPermissionsShowPrompt instead') - Future Function( - InAppWebViewController controller, String origin)? - androidOnGeolocationPermissionsShowPrompt, - @Deprecated('Use onPermissionRequest instead') - Future Function( - InAppWebViewController controller, - String origin, - List resources)? - androidOnPermissionRequest, - @Deprecated('Use onSafeBrowsingHit instead') - Future Function(InAppWebViewController controller, - Uri url, SafeBrowsingThreat? threatType)? - androidOnSafeBrowsingHit, - InAppWebViewInitialData? initialData, - String? initialFile, - @Deprecated('Use initialSettings instead') - InAppWebViewGroupOptions? initialOptions, - InAppWebViewSettings? initialSettings, - URLRequest? initialUrlRequest, - UnmodifiableListView? initialUserScripts, - PullToRefreshController? pullToRefreshController, - FindInteractionController? findInteractionController, - ContextMenu? contextMenu, - void Function(InAppWebViewController controller, WebUri? url)? - onPageCommitVisible, - void Function(InAppWebViewController controller, String? title)? - onTitleChanged, - @Deprecated( - 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') - void Function(InAppWebViewController controller)? - iosOnDidReceiveServerRedirectForProvisionalNavigation, - @Deprecated('Use onWebContentProcessDidTerminate instead') - void Function(InAppWebViewController controller)? - iosOnWebContentProcessDidTerminate, - @Deprecated('Use onNavigationResponse instead') - Future Function( - InAppWebViewController controller, - IOSWKNavigationResponse navigationResponse)? - iosOnNavigationResponse, - @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - iosShouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxProgress, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxReadyStateChange, - void Function( - InAppWebViewController controller, ConsoleMessage consoleMessage)? - onConsoleMessage, - Future Function(InAppWebViewController controller, - CreateWindowAction createWindowAction)? - onCreateWindow, - void Function(InAppWebViewController controller)? onCloseWindow, - void Function(InAppWebViewController controller)? onWindowFocus, - void Function(InAppWebViewController controller)? onWindowBlur, - @Deprecated('Use onReceivedIcon instead') - void Function(InAppWebViewController controller, Uint8List icon)? - androidOnReceivedIcon, - @Deprecated('Use onReceivedTouchIconUrl instead') - void Function(InAppWebViewController controller, Uri url, bool precomposed)? - androidOnReceivedTouchIconUrl, - @Deprecated('Use onDownloadStartRequest instead') - void Function(InAppWebViewController controller, Uri url)? onDownloadStart, - void Function(InAppWebViewController controller, - DownloadStartRequest downloadStartRequest)? - onDownloadStartRequest, - @Deprecated('Use FindInteractionController.onFindResultReceived instead') - void Function(InAppWebViewController controller, int activeMatchOrdinal, - int numberOfMatches, bool isDoneCounting)? - onFindResultReceived, - Future Function( - InAppWebViewController controller, JsAlertRequest jsAlertRequest)? - onJsAlert, - Future Function(InAppWebViewController controller, - JsConfirmRequest jsConfirmRequest)? - onJsConfirm, - Future Function( - InAppWebViewController controller, JsPromptRequest jsPromptRequest)? - onJsPrompt, - @Deprecated("Use onReceivedError instead") - void Function(InAppWebViewController controller, Uri? url, int code, - String message)? - onLoadError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceError error)? - onReceivedError, - @Deprecated("Use onReceivedHttpError instead") - void Function(InAppWebViewController controller, Uri? url, int statusCode, - String description)? - onLoadHttpError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceResponse errorResponse)? - onReceivedHttpError, - void Function(InAppWebViewController controller, LoadedResource resource)? - onLoadResource, - @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future Function( - InAppWebViewController controller, Uri url)? - onLoadResourceCustomScheme, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - onLoadResourceWithCustomScheme, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, - void Function(InAppWebViewController controller, - InAppWebViewHitTestResult hitTestResult)? - onLongPressHitTestResult, - @Deprecated("Use onPrintRequest instead") - void Function(InAppWebViewController controller, Uri? url)? onPrint, - Future Function(InAppWebViewController controller, WebUri? url, - PlatformPrintJobController? printJobController)? - onPrintRequest, - void Function(InAppWebViewController controller, int progress)? - onProgressChanged, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedClientCertRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedHttpAuthRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedServerTrustAuthRequest, - void Function(InAppWebViewController controller, int x, int y)? - onScrollChanged, - void Function( - InAppWebViewController controller, WebUri? url, bool? isReload)? - onUpdateVisitedHistory, - void Function(InAppWebViewController controller)? onWebViewCreated, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - shouldInterceptAjaxRequest, - Future Function( - InAppWebViewController controller, FetchRequest fetchRequest)? - shouldInterceptFetchRequest, - Future Function(InAppWebViewController controller, - NavigationAction navigationAction)? - shouldOverrideUrlLoading, - void Function(InAppWebViewController controller)? onEnterFullscreen, - void Function(InAppWebViewController controller)? onExitFullscreen, - void Function(InAppWebViewController controller, int x, int y, - bool clampedX, bool clampedY)? - onOverScrolled, - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - onZoomScaleChanged, - @Deprecated('Use shouldInterceptRequest instead') - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - androidShouldInterceptRequest, - @Deprecated('Use onRenderProcessUnresponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessUnresponsive, - @Deprecated('Use onRenderProcessResponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessResponsive, - @Deprecated('Use onRenderProcessGone instead') - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - androidOnRenderProcessGone, - @Deprecated('Use onFormResubmission instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnFormResubmission, - @Deprecated('Use onZoomScaleChanged instead') - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - androidOnScaleChanged, - @Deprecated('Use onJsBeforeUnload instead') - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - androidOnJsBeforeUnload, - @Deprecated('Use onReceivedLoginRequest instead') - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - androidOnReceivedLoginRequest, - void Function(InAppWebViewController controller)? - onDidReceiveServerRedirectForProvisionalNavigation, - Future Function( - InAppWebViewController controller, WebUri? url)? - onFormResubmission, - void Function(InAppWebViewController controller)? - onGeolocationPermissionsHidePrompt, - Future Function( - InAppWebViewController controller, String origin)? - onGeolocationPermissionsShowPrompt, - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - onJsBeforeUnload, - Future Function( - InAppWebViewController controller, - NavigationResponse navigationResponse)? - onNavigationResponse, - Future Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequest, - void Function(InAppWebViewController controller, Uint8List icon)? - onReceivedIcon, - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - onReceivedLoginRequest, - void Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequestCanceled, - void Function(InAppWebViewController controller)? onRequestFocus, - void Function( - InAppWebViewController controller, WebUri url, bool precomposed)? - onReceivedTouchIconUrl, - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - onRenderProcessGone, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessResponsive, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessUnresponsive, - Future Function(InAppWebViewController controller, - WebUri url, SafeBrowsingThreat? threatType)? - onSafeBrowsingHit, - void Function(InAppWebViewController controller)? - onWebContentProcessDidTerminate, - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - shouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - shouldInterceptRequest, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onCameraCaptureStateChanged, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onMicrophoneCaptureStateChanged, - void Function(InAppWebViewController controller, Size oldContentSize, - Size newContentSize)? - onContentSizeChanged, - }) : this.fromPlatformCreationParams( + InAppWebView( + {Key? key, + Set>? gestureRecognizers, + int? windowId, + HeadlessInAppWebView? headlessWebView, + InAppWebViewKeepAlive? keepAlive, + bool? preventGestureDelay, + TextDirection? layoutDirection, + WebViewEnvironment? webViewEnvironment, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + void Function(InAppWebViewController controller)? + androidOnGeolocationPermissionsHidePrompt, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + FutureOr Function( + InAppWebViewController controller, String origin)? + androidOnGeolocationPermissionsShowPrompt, + @Deprecated('Use onPermissionRequest instead') + FutureOr Function( + InAppWebViewController controller, + String origin, + List resources)? + androidOnPermissionRequest, + @Deprecated('Use onSafeBrowsingHit instead') + FutureOr Function( + InAppWebViewController controller, + Uri url, + SafeBrowsingThreat? threatType)? + androidOnSafeBrowsingHit, + InAppWebViewInitialData? initialData, + String? initialFile, + @Deprecated('Use initialSettings instead') + InAppWebViewGroupOptions? initialOptions, + InAppWebViewSettings? initialSettings, + URLRequest? initialUrlRequest, + UnmodifiableListView? initialUserScripts, + PullToRefreshController? pullToRefreshController, + FindInteractionController? findInteractionController, + ContextMenu? contextMenu, + void Function(InAppWebViewController controller, WebUri? url)? + onPageCommitVisible, + void Function(InAppWebViewController controller, String? title)? + onTitleChanged, + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') + void Function(InAppWebViewController controller)? + iosOnDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onWebContentProcessDidTerminate instead') + void Function(InAppWebViewController controller)? + iosOnWebContentProcessDidTerminate, + @Deprecated('Use onNavigationResponse instead') + FutureOr Function(InAppWebViewController controller, IOSWKNavigationResponse navigationResponse)? iosOnNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? iosShouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxProgress, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxReadyStateChange, + void Function(InAppWebViewController controller, ConsoleMessage consoleMessage)? onConsoleMessage, + FutureOr Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow, + void Function(InAppWebViewController controller)? onCloseWindow, + void Function(InAppWebViewController controller)? onWindowFocus, + void Function(InAppWebViewController controller)? onWindowBlur, + @Deprecated('Use onReceivedIcon instead') void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') void Function(InAppWebViewController controller, Uri url, bool precomposed)? androidOnReceivedTouchIconUrl, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, Uri url)? onDownloadStart, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStartRequest, + FutureOr Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStarting, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') void Function(InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting)? onFindResultReceived, + FutureOr Function(InAppWebViewController controller, JsAlertRequest jsAlertRequest)? onJsAlert, + FutureOr Function(InAppWebViewController controller, JsConfirmRequest jsConfirmRequest)? onJsConfirm, + FutureOr Function(InAppWebViewController controller, JsPromptRequest jsPromptRequest)? onJsPrompt, + @Deprecated("Use onReceivedError instead") void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceError error)? onReceivedError, + @Deprecated("Use onReceivedHttpError instead") void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError, + void Function(InAppWebViewController controller, LoadedResource resource)? onLoadResource, + @Deprecated('Use onLoadResourceWithCustomScheme instead') FutureOr Function(InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, + void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult, + @Deprecated("Use onPrintRequest instead") void Function(InAppWebViewController controller, Uri? url)? onPrint, + FutureOr Function(InAppWebViewController controller, WebUri? url, PlatformPrintJobController? printJobController)? onPrintRequest, + void Function(InAppWebViewController controller, int progress)? onProgressChanged, + FutureOr Function(InAppWebViewController controller, ClientCertChallenge challenge)? onReceivedClientCertRequest, + FutureOr Function(InAppWebViewController controller, HttpAuthenticationChallenge challenge)? onReceivedHttpAuthRequest, + FutureOr Function(InAppWebViewController controller, ServerTrustChallenge challenge)? onReceivedServerTrustAuthRequest, + void Function(InAppWebViewController controller, int x, int y)? onScrollChanged, + void Function(InAppWebViewController controller, WebUri? url, bool? isReload)? onUpdateVisitedHistory, + void Function(InAppWebViewController controller)? onWebViewCreated, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? shouldInterceptAjaxRequest, + FutureOr Function(InAppWebViewController controller, FetchRequest fetchRequest)? shouldInterceptFetchRequest, + FutureOr Function(InAppWebViewController controller, NavigationAction navigationAction)? shouldOverrideUrlLoading, + void Function(InAppWebViewController controller)? onEnterFullscreen, + void Function(InAppWebViewController controller)? onExitFullscreen, + void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled, + void Function(InAppWebViewController controller, double oldScale, double newScale)? onZoomScaleChanged, + @Deprecated('Use shouldInterceptRequest instead') FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? androidShouldInterceptRequest, + @Deprecated('Use onRenderProcessUnresponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessUnresponsive, + @Deprecated('Use onRenderProcessResponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessResponsive, + @Deprecated('Use onRenderProcessGone instead') void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? androidOnRenderProcessGone, + @Deprecated('Use onFormResubmission instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') void Function(InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged, + @Deprecated('Use onJsBeforeUnload instead') FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? androidOnJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') void Function(InAppWebViewController controller, LoginRequest loginRequest)? androidOnReceivedLoginRequest, + void Function(InAppWebViewController controller)? onDidReceiveServerRedirectForProvisionalNavigation, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onFormResubmission, + void Function(InAppWebViewController controller)? onGeolocationPermissionsHidePrompt, + FutureOr Function(InAppWebViewController controller, String origin)? onGeolocationPermissionsShowPrompt, + FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? onJsBeforeUnload, + FutureOr Function(InAppWebViewController controller, NavigationResponse navigationResponse)? onNavigationResponse, + FutureOr Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest, + void Function(InAppWebViewController controller, Uint8List icon)? onReceivedIcon, + void Function(InAppWebViewController controller, LoginRequest loginRequest)? onReceivedLoginRequest, + void Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequestCanceled, + void Function(InAppWebViewController controller)? onRequestFocus, + void Function(InAppWebViewController controller, WebUri url, bool precomposed)? onReceivedTouchIconUrl, + void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? onRenderProcessGone, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessResponsive, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessUnresponsive, + FutureOr Function(InAppWebViewController controller, WebUri url, SafeBrowsingThreat? threatType)? onSafeBrowsingHit, + void Function(InAppWebViewController controller)? onWebContentProcessDidTerminate, + FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? shouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged, + void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, + void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) + : this.fromPlatformCreationParams( key: key, params: PlatformInAppWebViewWidgetCreationParams( controllerFromPlatform: @@ -310,6 +186,7 @@ class InAppWebView extends StatefulWidget { findInteractionController: findInteractionController?.platform, contextMenu: contextMenu, layoutDirection: layoutDirection, + webViewEnvironment: webViewEnvironment?.platform, onWebViewCreated: onWebViewCreated != null ? (controller) => onWebViewCreated.call(controller) : null, @@ -362,6 +239,10 @@ class InAppWebView extends StatefulWidget { ? (controller, downloadStartRequest) => onDownloadStartRequest .call(controller, downloadStartRequest) : null, + onDownloadStarting: onDownloadStarting != null + ? (controller, downloadStartRequest) => + onDownloadStarting.call(controller, downloadStartRequest) + : null, onLoadResourceCustomScheme: onLoadResourceCustomScheme != null ? (controller, url) => onLoadResourceCustomScheme.call(controller, url) @@ -653,6 +534,18 @@ class InAppWebView extends StatefulWidget { onContentSizeChanged.call( controller, oldContentSize, newContentSize) : null, + onProcessFailed: onProcessFailed != null + ? (controller, detail) => + onProcessFailed.call(controller, detail) + : null, + onAcceleratorKeyPressed: onAcceleratorKeyPressed != null + ? (controller, detail) => + onAcceleratorKeyPressed.call(controller, detail) + : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, gestureRecognizers: gestureRecognizers, headlessWebView: headlessWebView?.platform, preventGestureDelay: preventGestureDelay, diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart index ee98e38d9..ef61cc4d0 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,18 +1,16 @@ import 'dart:core'; -import 'dart:typed_data'; -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 '../print_job/main.dart'; import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; + import 'android/in_app_webview_controller.dart'; import 'apple/in_app_webview_controller.dart'; -import '../print_job/main.dart'; - ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController} class InAppWebViewController { ///Use [InAppWebViewController] instead. @@ -166,14 +164,12 @@ class InAppWebViewController { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.addJavaScriptHandler} void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) => + {required String handlerName, required Function callback}) => platform.addJavaScriptHandler( handlerName: handlerName, callback: callback); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.removeJavaScriptHandler} - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) => + Function? removeJavaScriptHandler({required String handlerName}) => platform.removeJavaScriptHandler(handlerName: handlerName); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.hasJavaScriptHandler} @@ -290,9 +286,26 @@ class InAppWebViewController { Future getHitTestResult() => platform.getHitTestResult(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.requestFocus} + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) => + platform.requestFocus( + direction: direction, previouslyFocusedRect: previouslyFocusedRect); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.clearFocus} Future clearFocus() => platform.clearFocus(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setInputMethodEnabled} + Future setInputMethodEnabled(bool enabled) => + platform.setInputMethodEnabled(enabled); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.showInputMethod} + Future showInputMethod() => platform.showInputMethod(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.hideInputMethod} + Future hideInputMethod() => platform.hideInputMethod(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setContextMenu} Future setContextMenu(ContextMenu? contextMenu) => platform.setContextMenu(contextMenu); @@ -484,6 +497,37 @@ class InAppWebViewController { URLResponse? urlResponse}) => platform.loadSimulatedRequest(urlRequest: urlRequest, data: data); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.openDevTools} + Future openDevTools() => platform.openDevTools(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.callDevToolsProtocolMethod} + Future callDevToolsProtocolMethod( + {required String methodName, Map? parameters}) => + platform.callDevToolsProtocolMethod( + methodName: methodName, parameters: parameters); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.addDevToolsProtocolEventListener} + Future addDevToolsProtocolEventListener( + {required String eventName, + required Function(dynamic data) callback}) => + platform.addDevToolsProtocolEventListener( + eventName: eventName, callback: callback); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.removeDevToolsProtocolEventListener} + Future removeDevToolsProtocolEventListener( + {required String eventName}) => + platform.removeDevToolsProtocolEventListener(eventName: eventName); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.isInterfaceSupported} + Future isInterfaceSupported(WebViewInterface interface) => + platform.isInterfaceSupported(interface); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.saveState} + Future saveState() => platform.saveState(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.restoreState} + Future restoreState(Uint8List state) => platform.restoreState(state); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getIFrameId} Future getIFrameId() => platform.getIFrameId(); @@ -544,6 +588,19 @@ class InAppWebViewController { PlatformInAppWebViewController.static() .clearAllCache(includeDiskFiles: includeDiskFiles); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.enableSlowWholeDocumentDraw} + static Future enableSlowWholeDocumentDraw() => + PlatformInAppWebViewController.static().enableSlowWholeDocumentDraw(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setJavaScriptBridgeName} + static Future setJavaScriptBridgeName(String bridgeName) => + PlatformInAppWebViewController.static() + .setJavaScriptBridgeName(bridgeName); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getJavaScriptBridgeName} + static Future getJavaScriptBridgeName() => + PlatformInAppWebViewController.static().getJavaScriptBridgeName(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.tRexRunnerHtml} static Future get tRexRunnerHtml => PlatformInAppWebViewController.static().tRexRunnerHtml; diff --git a/flutter_inappwebview/lib/src/main.dart b/flutter_inappwebview/lib/src/main.dart index 2b968fee7..64e28fb3f 100644 --- a/flutter_inappwebview/lib/src/main.dart +++ b/flutter_inappwebview/lib/src/main.dart @@ -15,3 +15,4 @@ export 'webview_asset_loader.dart'; export 'tracing_controller.dart'; export 'process_global_config.dart'; export 'in_app_localhost_server.dart'; +export 'webview_environment/main.dart'; diff --git a/flutter_inappwebview/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview/lib/src/print_job/print_job_controller.dart index 427d1e8cc..ded9521e0 100644 --- a/flutter_inappwebview/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview/lib/src/print_job/print_job_controller.dart @@ -3,10 +3,9 @@ import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_pla ///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} class PrintJobController { ///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} - PrintJobController({required String id, PrintJobCompletionHandler onComplete}) + PrintJobController({required String id}) : this.fromPlatformCreationParams( - params: PlatformPrintJobControllerCreationParams( - id: id, onComplete: onComplete)); + params: PlatformPrintJobControllerCreationParams(id: id)); /// Constructs a [PrintJobController]. /// diff --git a/flutter_inappwebview/lib/src/web_message/web_message_port.dart b/flutter_inappwebview/lib/src/web_message/web_message_port.dart index 5d48b78e5..f9f5dba91 100644 --- a/flutter_inappwebview/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview/lib/src/web_message/web_message_port.dart @@ -31,7 +31,8 @@ class WebMessagePort implements IWebMessagePort { ///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort.close} Future close() => platform.close(); - Map toMap() => platform.toMap(); + Map toMap({EnumMethod? enumMethod}) => + platform.toMap(enumMethod: enumMethod); Map toJson() => platform.toJson(); diff --git a/flutter_inappwebview/lib/src/webview_asset_loader.dart b/flutter_inappwebview/lib/src/webview_asset_loader.dart index 7ccc155ab..2e42d9e4c 100644 --- a/flutter_inappwebview/lib/src/webview_asset_loader.dart +++ b/flutter_inappwebview/lib/src/webview_asset_loader.dart @@ -26,7 +26,8 @@ abstract class PathHandler } @override - Map toMap() => platform.toMap(); + Map toMap({EnumMethod? enumMethod}) => + platform.toMap(enumMethod: enumMethod); @override Map toJson() => platform.toJson(); diff --git a/flutter_inappwebview/lib/src/webview_environment/main.dart b/flutter_inappwebview/lib/src/webview_environment/main.dart new file mode 100644 index 000000000..f9bf33a73 --- /dev/null +++ b/flutter_inappwebview/lib/src/webview_environment/main.dart @@ -0,0 +1 @@ +export 'webview_environment.dart'; diff --git a/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart new file mode 100644 index 000000000..141804c37 --- /dev/null +++ b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart @@ -0,0 +1,81 @@ +import 'dart:core'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment} +class WebViewEnvironment { + /// Constructs a [WebViewEnvironment]. + /// + /// See [WebViewEnvironment.fromPlatformCreationParams] for setting parameters for + /// a specific platform. + WebViewEnvironment.fromPlatformCreationParams({ + required PlatformWebViewEnvironmentCreationParams params, + }) : this.fromPlatform(platform: PlatformWebViewEnvironment(params)); + + /// Constructs a [WebViewEnvironment] from a specific platform implementation. + WebViewEnvironment.fromPlatform({required this.platform}); + + /// Implementation of [PlatformWebViewEnvironment] for the current platform. + final PlatformWebViewEnvironment platform; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.id} + String get id => platform.id; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + WebViewEnvironmentSettings? get settings => platform.settings; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isInterfaceSupported} + Future isInterfaceSupported(WebViewInterface interface) => + platform.isInterfaceSupported(interface); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + Future> getProcessInfos() => + platform.getProcessInfos(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getFailureReportFolderPath} + Future getFailureReportFolderPath() => + platform.getFailureReportFolderPath(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} + static Future create( + {WebViewEnvironmentSettings? settings}) async { + return WebViewEnvironment.fromPlatform( + platform: await PlatformWebViewEnvironment.static() + .create(settings: settings)); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + static Future getAvailableVersion( + {String? browserExecutableFolder}) => + PlatformWebViewEnvironment.static().getAvailableVersion( + browserExecutableFolder: browserExecutableFolder); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + static Future compareBrowserVersions( + {required String version1, required String version2}) => + PlatformWebViewEnvironment.static() + .compareBrowserVersions(version1: version1, version2: version2); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onNewBrowserVersionAvailable} + void Function()? get onNewBrowserVersionAvailable => + platform.onNewBrowserVersionAvailable; + set onNewBrowserVersionAvailable(void Function()? value) => + platform.onNewBrowserVersionAvailable = value; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onBrowserProcessExited} + void Function(BrowserProcessExitedDetail detail)? + get onBrowserProcessExited => platform.onBrowserProcessExited; + set onBrowserProcessExited( + void Function(BrowserProcessExitedDetail detail)? value) => + platform.onBrowserProcessExited = value; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onProcessInfosChanged} + void Function(BrowserProcessInfosChangedDetail detail)? + get onProcessInfosChanged => platform.onProcessInfosChanged; + set onProcessInfosChanged( + void Function(BrowserProcessInfosChangedDetail detail)? value) => + platform.onProcessInfosChanged = value; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} + Future dispose() => platform.dispose(); +} diff --git a/flutter_inappwebview/pubspec.yaml b/flutter_inappwebview/pubspec.yaml index ef480d4a4..15023b3da 100755 --- a/flutter_inappwebview/pubspec.yaml +++ b/flutter_inappwebview/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 6.0.0 +version: 6.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,25 +14,32 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.0.10 - flutter_inappwebview_android: ^1.0.12 - flutter_inappwebview_ios: ^1.0.13 - flutter_inappwebview_macos: ^1.0.11 - flutter_inappwebview_web: ^1.0.8 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface + flutter_inappwebview_android: #^1.2.0-beta.3 + path: ../flutter_inappwebview_android + flutter_inappwebview_ios: #^1.2.0-beta.3 + path: ../flutter_inappwebview_ios + flutter_inappwebview_macos: #^1.2.0-beta.3 + path: ../flutter_inappwebview_macos + flutter_inappwebview_web: #^1.2.0-beta.3 + path: ../flutter_inappwebview_web + flutter_inappwebview_windows: #^0.7.0-beta.3 + path: ../flutter_inappwebview_windows dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter - flutter_lints: ^2.0.1 - build_runner: ^2.4.0 + flutter_lints: ^4.0.0 + build_runner: ^2.4.12 generators: path: ../dev_packages/generators @@ -49,6 +58,8 @@ flutter: default_package: flutter_inappwebview_macos web: default_package: flutter_inappwebview_web + windows: + default_package: flutter_inappwebview_windows assets: - packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html diff --git a/flutter_inappwebview_android/CHANGELOG.md b/flutter_inappwebview_android/CHANGELOG.md index 3b44af454..fb2d473f3 100644 --- a/flutter_inappwebview_android/CHANGELOG.md +++ b/flutter_inappwebview_android/CHANGELOG.md @@ -1,3 +1,79 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event +- Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `hideInputMethod`, `showInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "[Android] PrintJobOrientation _TypeError (type 'Null' is not a subtype of type 'int')" [#2413](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2413) +- Fixed "Accessibility Android" [#1694](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1694) +- Fixed "Automatic font scale according to accessibility option 'font size' of device does not work on Android" [#540](https://github.com/pichillilorenzo/flutter_inappwebview/issues/540) +- Fixed "callHandler method is not injected into InAppBrowser" [#1973](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1973) + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Added `InAppWebViewController.enableSlowWholeDocumentDraw` static method +- Added `CookieManager.flush` method +- Added support for `UserScript.forMainFrameOnly` parameter +- Implemented `requestFocus` WebView method +- Updated UserScript at document end implementation +- Updated `InAppWebViewController.takeScreenshot` implementation to support screenshot out of visible viewport when `InAppWebViewController.enableSlowWholeDocumentDraw` is called +- Fixed "After dispose a InAppWebViewKeepAlive using InAppWebViewController.disposeKeepAlive. NullPointerException is thrown when main activity enter destroyed state." [#2025](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2025) +- Fixed crash when trying to open InAppBrowser with R.menu.menu_main on release mode +- Fixed "android.webkit.WebSettingsWrapper cannot be cast to com.android.webview.chromium.ContentSettingsAdapter" [#2397](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2397) +- Merged "Prevent blank InAppBrowser Activity from being restored" [#1984](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1984) (thanks to [ShuheiSuzuki-07](https://github.com/ShuheiSuzuki-07)) +- Merged "Update Android Cookie Expiration date format to 24-hour format (HH)" [#2389](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2389) (thanks to [takuyaaaaaaahaaaaaa](https://github.com/takuyaaaaaaahaaaaaa)) +- Merged "[Android] allow sync navigation requests using a regular expression" [#2008](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2008) (thanks to [lyb5834](https://github.com/lyb5834)) + +## 1.1.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.3.0 + +## 1.1.2 + +- Removed webview/plugin_scripts_js/ConsoleLogJS.java file, use native WebChromeClient.onConsoleMessage instead + +## 1.1.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.2.0 + +## 1.1.0+4 + +- Updated flutter_inappwebview_platform_interface version + +## 1.1.0+3 + +- Fixed compilation error + +## 1.1.0+2 + +- Updated pubspec.yaml + +## 1.1.0+1 + +- Downgraded androidx.appcompat:appcompat:1.7.0 to androidx.appcompat:appcompat:1.6.1 +- Added `-dontwarn android.window.BackEvent` proguard rule + +## 1.1.0 + +- Updated androidx.webkit:webkit:1.8.0 to androidx.webkit:webkit:1.12.0 +- Updated androidx.browser:browser:1.6.0 to androidx.browser:browser:1.8.0 +- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. +- Removed unsupported WebViewFeature.SUPPRESS_ERROR_PAGE +- Merged "Remove references to deprecated v1 Android embedding" [#2176](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2176) (thanks to [gmackall](https://github.com/gmackall)) + +## 1.0.13 + +- Fixed "Android emulator using API 34 fails to draw on resume sometimes" [#1981](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1981) + ## 1.0.12 - Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10` diff --git a/flutter_inappwebview_android/android/build.gradle b/flutter_inappwebview_android/android/build.gradle index 2dfb9459f..ddb812ca2 100755 --- a/flutter_inappwebview_android/android/build.gradle +++ b/flutter_inappwebview_android/android/build.gradle @@ -26,7 +26,7 @@ android { if (project.android.hasProperty("namespace")) { namespace 'com.pichillilorenzo.flutter_inappwebview_android' } - compileSdkVersion 33 + compileSdk 34 defaultConfig { minSdkVersion 19 @@ -49,8 +49,8 @@ android { } } dependencies { - implementation 'androidx.webkit:webkit:1.8.0' - implementation 'androidx.browser:browser:1.6.0' + implementation 'androidx.webkit:webkit:1.12.0' + implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' } diff --git a/flutter_inappwebview_android/android/proguard-rules.pro b/flutter_inappwebview_android/android/proguard-rules.pro index 9918f23d6..a806bdcc4 100755 --- a/flutter_inappwebview_android/android/proguard-rules.pro +++ b/flutter_inappwebview_android/android/proguard-rules.pro @@ -14,4 +14,6 @@ public *; private *; } --keep class com.pichillilorenzo.flutter_inappwebview_android.** { *; } \ No newline at end of file +-keep class com.pichillilorenzo.flutter_inappwebview_android.** { *; } + +-dontwarn android.window.BackEvent \ No newline at end of file diff --git a/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java b/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java new file mode 100644 index 000000000..2d3178171 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java @@ -0,0 +1,123 @@ +package android.print; + +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class InAppWebViewPrintDocumentAdapter extends PrintDocumentAdapter { + @NonNull + private final PrintDocumentAdapter delegate; + @Nullable + private final PrintDocumentAdapterCallback callback; + + public InAppWebViewPrintDocumentAdapter(@NonNull PrintDocumentAdapter delegate, @Nullable PrintDocumentAdapterCallback callback) { + this.delegate = delegate; + this.callback = callback; + } + + @Override + public void onStart() { + this.delegate.onStart(); + if (this.callback != null) this.callback.onStart(); + } + + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback layoutResultCallback, Bundle extras) { + this.delegate.onLayout(oldAttributes, newAttributes, cancellationSignal, new LayoutResultCallback() { + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + layoutResultCallback.onLayoutFinished(info, changed); + if (callback != null) callback.onLayoutFinished(info, changed); + } + + @Override + public void onLayoutFailed(CharSequence error) { + layoutResultCallback.onLayoutFailed(error); + if (callback != null) callback.onLayoutFailed(error); + } + + @Override + public void onLayoutCancelled() { + layoutResultCallback.onLayoutCancelled(); + if (callback != null) callback.onLayoutCancelled(); + } + }, extras); + + if (callback != null) callback.onLayout(oldAttributes, newAttributes, cancellationSignal, layoutResultCallback, extras); + } + + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { + this.delegate.onWrite(pages, destination, cancellationSignal, new WriteResultCallback() { + @Override + public void onWriteFinished(PageRange[] pages) { + writeResultCallback.onWriteFinished(pages); + if (callback != null) callback.onWriteFinished(pages); + } + + @Override + public void onWriteFailed(CharSequence error) { + writeResultCallback.onWriteFailed(error); + if (callback != null) callback.onWriteFailed(error); + } + + @Override + public void onWriteCancelled() { + writeResultCallback.onWriteCancelled(); + if (callback != null) callback.onWriteCancelled(); + } + }); + if (callback != null) callback.onWrite(pages, destination, cancellationSignal, writeResultCallback); + } + + @Override + public void onFinish() { + this.delegate.onFinish(); + if (this.callback != null) this.callback.onFinish(); + } + + public static class PrintDocumentAdapterCallback { + public void onStart() { + /* do nothing - stub */ + } + + public void onFinish() { + /* do nothing - stub */ + } + + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback layoutResultCallback, Bundle extras) { + /* do nothing - stub */ + } + + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + /* do nothing - stub */ + } + + public void onLayoutFailed(CharSequence error) { + /* do nothing - stub */ + } + + public void onLayoutCancelled() { + /* do nothing - stub */ + } + + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { + /* do nothing - stub */ + } + + public void onWriteFinished(PageRange[] pages) { + /* do nothing - stub */ + } + + public void onWriteFailed(CharSequence error) { + /* do nothing - stub */ + } + + public void onWriteCancelled() { + /* do nothing - stub */ + } + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/InAppWebViewFlutterPlugin.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/InAppWebViewFlutterPlugin.java index b475a0e3d..21ce254a9 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/InAppWebViewFlutterPlugin.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/InAppWebViewFlutterPlugin.java @@ -24,9 +24,8 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterView; +import io.flutter.embedding.android.FlutterView; public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { @@ -64,26 +63,16 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { public ProcessGlobalConfigManager processGlobalConfigManager; public FlutterWebViewFactory flutterWebViewFactory; public Context applicationContext; - public PluginRegistry.Registrar registrar; public BinaryMessenger messenger; public FlutterPlugin.FlutterAssets flutterAssets; @Nullable public ActivityPluginBinding activityPluginBinding; @Nullable public Activity activity; - @SuppressWarnings("deprecation") public FlutterView flutterView; public InAppWebViewFlutterPlugin() {} - @SuppressWarnings("deprecation") - public static void registerWith(PluginRegistry.Registrar registrar) { - final InAppWebViewFlutterPlugin instance = new InAppWebViewFlutterPlugin(); - instance.registrar = registrar; - instance.onAttachedToEngine( - registrar.context(), registrar.messenger(), registrar.activity(), registrar.platformViewRegistry(), registrar.view()); - } - @Override public void onAttachedToEngine(FlutterPluginBinding binding) { this.flutterAssets = binding.getFlutterAssets(); @@ -96,7 +85,6 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { binding.getApplicationContext(), binding.getBinaryMessenger(), this.activity, binding.getPlatformViewRegistry(), null); } - @SuppressWarnings("deprecation") private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger, Activity activity, PlatformViewRegistry platformViewRegistry, FlutterView flutterView) { this.applicationContext = applicationContext; this.activity = activity; diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java index ff4247e7e..dccb7eabf 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java @@ -103,6 +103,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case "removeSessionCookies": removeSessionCookies(result); break; + case "flush": + flush(result); + break; default: result.notImplemented(); } @@ -250,7 +253,7 @@ public List> getCookies(final String url) { if (cookieParamName.equalsIgnoreCase("Expires")) { try { - final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US); + final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); Date expiryDate = sdf.parse(cookieParamValue); if (expiryDate != null) { cookieMap.put("expiresDate", expiryDate.getTime()); @@ -423,8 +426,22 @@ else if (plugin != null) { } } + public void flush(MethodChannel.Result result) { + cookieManager = getCookieManager(); + if (cookieManager == null) { + result.success(false); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cookieManager.flush(); + } else if (plugin != null) { + CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext); + cookieSyncMngr.sync(); + } + } + public static String getCookieExpirationDate(Long timestamp) { - final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US); + final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); return sdf.format(new Date(timestamp)); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/Util.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/Util.java index a5378b7c8..153156212 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/Util.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/Util.java @@ -66,7 +66,7 @@ public class Util { private Util() {} public static String getUrlAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException { - String key = (plugin.registrar != null) ? plugin.registrar.lookupKeyForAsset(assetFilePath) : plugin.flutterAssets.getAssetFilePathByName(assetFilePath); + String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath); InputStream is = null; IOException e = null; @@ -91,7 +91,7 @@ public static String getUrlAsset(InAppWebViewFlutterPlugin plugin, String assetF } public static InputStream getFileAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException { - String key = (plugin.registrar != null) ? plugin.registrar.lookupKeyForAsset(assetFilePath) : plugin.flutterAssets.getAssetFilePathByName(assetFilePath); + String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath); AssetManager mg = plugin.applicationContext.getResources().getAssets(); return mg.open(key); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java index 444bc25d5..aae335ebb 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java @@ -78,13 +78,23 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.chrome_custom_tabs_layout); Bundle b = getIntent().getExtras(); - if (b == null) return; + if (b == null) { + if (savedInstanceState != null) { + close(); + } + return; + } id = b.getString("id"); String managerId = b.getString("managerId"); manager = ChromeSafariBrowserManager.shared.get(managerId); - if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return; + if (manager == null || manager.plugin == null || manager.plugin.messenger == null) { + if (savedInstanceState != null) { + close(); + } + return; + } manager.browsers.put(id, this); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java index 8564dbe82..e4486848a 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java @@ -157,9 +157,9 @@ public void run() { final String cssSelector = action.getSelector(); final String jsScript = "(function(d) { " + " function hide () { " + - " if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style')) { " + + " if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "-css-display-none-style')) { " + " var c = d.createElement('style'); " + - " c.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style'; " + + " c.id = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "-css-display-none-style'; " + " c.innerHTML = '" + cssSelector + " { display: none !important; }'; " + " d.body.appendChild(c); " + " }" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java index 6cb763c49..8c5ba07ae 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java @@ -51,7 +51,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrowserDelegate, Disposable { protected static final String LOG_TAG = "InAppBrowserActivity"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_"; - + @Nullable public Integer windowId; public String id; @@ -77,19 +77,29 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow @Nullable public InAppBrowserChannelDelegate channelDelegate; public List menuItems = new ArrayList<>(); - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle b = getIntent().getExtras(); - if (b == null) return; - + if (b == null) { + if (savedInstanceState != null) { + finish(); + } + return; + } + id = b.getString("id"); String managerId = b.getString("managerId"); manager = InAppBrowserManager.shared.get(managerId); - if (manager == null || manager.plugin == null|| manager.plugin.messenger == null) return; + if (manager == null || manager.plugin == null || manager.plugin.messenger == null) { + if (savedInstanceState != null) { + finish(); + } + return; + } Map settingsMap = (Map) b.getSerializable("settings"); customSettings.parse(settingsMap); @@ -106,10 +116,12 @@ protected void onCreate(Bundle savedInstanceState) { pullToRefreshLayout.channelDelegate = new PullToRefreshChannelDelegate(pullToRefreshLayout, pullToRefreshLayoutChannel); pullToRefreshLayout.settings = pullToRefreshSettings; pullToRefreshLayout.prepare(); - + webView = findViewById(R.id.webView); webView.id = id; - webView.windowId = windowId; + if (windowId != -1) { + webView.windowId = windowId; + } webView.inAppBrowserDelegate = this; webView.plugin = manager.plugin; @@ -166,15 +178,13 @@ protected void onCreate(Bundle savedInstanceState) { Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e); return; } - } - else if (initialData != null) { + } else if (initialData != null) { String mimeType = b.getString("initialMimeType"); String encoding = b.getString("initialEncoding"); String baseUrl = b.getString("initialBaseUrl"); String historyUrl = b.getString("initialHistoryUrl"); webView.loadDataWithBaseURL(baseUrl, initialData, mimeType, encoding, historyUrl); - } - else if (initialUrlRequest != null) { + } else if (initialUrlRequest != null) { URLRequest urlRequest = URLRequest.fromMap(initialUrlRequest); if (urlRequest != null) { webView.loadUrl(urlRequest); @@ -237,8 +247,15 @@ public boolean onCreateOptionsMenu(Menu m) { } MenuInflater inflater = getMenuInflater(); - // Inflate menu to add items to action bar if it is present. - inflater.inflate(R.menu.menu_main, menu); + try { + // Inflate menu to add items to action bar if it is present. + inflater.inflate(R.menu.menu_main, menu); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, "Cannot inflate com.pichillilorenzo.flutter_inappwebview_android.R.menu.menu_main." + + "To make it work, you need to set minifyEnabled false and shrinkResources false in your build.gradle file."); + return super.onCreateOptionsMenu(m); + } MenuItem menuSearchItem = menu.findItem(R.id.menu_search); if (menuSearchItem != null) { @@ -526,8 +543,8 @@ public void setSettings(InAppBrowserSettings newSettings, HashMap getCustomSettings() { - Map webViewSettingsMap = webView != null ? webView.getCustomSettings() : null; + public Map getCustomSettingsMap() { + Map webViewSettingsMap = webView != null ? webView.getCustomSettingsMap() : null; if (customSettings == null || webViewSettingsMap == null) return null; @@ -602,9 +619,9 @@ public List getActivityResultListeners() { } @Override - protected void onActivityResult (int requestCode, - int resultCode, - Intent data) { + protected void onActivityResult(int requestCode, + int resultCode, + Intent data) { for (ActivityResultListener listener : activityResultListeners) { if (listener.onActivityResult(requestCode, resultCode, data)) { return; diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/ConsoleLogJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/ConsoleLogJS.java deleted file mode 100644 index acc640871..000000000 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/ConsoleLogJS.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; - -import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; -import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; - -public class ConsoleLogJS { - public static final String CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT"; - public static final PluginScript CONSOLE_LOG_JS_PLUGIN_SCRIPT = new PluginScript( - ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, - ConsoleLogJS.CONSOLE_LOG_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); - - public static final String CONSOLE_LOG_JS_SOURCE = "(function(console) {" + - " function _buildMessage(args) {" + - " var message = '';" + - " for (var i in args) {" + - " try {" + - " message += message === '' ? args[i] : ' ' + args[i];" + - " } catch(ignored) {}" + - " }" + - " return message;" + - " }" + - " var oldLogs = {" + - " 'log': console.log," + - " 'debug': console.debug," + - " 'error': console.error," + - " 'info': console.info," + - " 'warn': console.warn" + - " };" + - " for (var k in oldLogs) {" + - " (function(oldLog) {" + - " console[oldLog] = function() {" + - " oldLogs[oldLog].call(console, _buildMessage(arguments));" + - " }" + - " })(k);" + - " }" + - "})(window.console);"; -} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java index 2ec2e0178..8ac66dcb4 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java @@ -1,266 +1,303 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class InterceptAjaxRequestJS { public static final String INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptAjaxRequest"; - public static final String FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._interceptOnlyAsyncAjaxRequests"; - public static final PluginScript INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript( - InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); + + public static String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useShouldInterceptAjaxRequest"; + } + + public static String FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnAjaxReadyStateChange"; + } + + public static String FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnAjaxProgress"; + } + + public static String FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._interceptOnlyAsyncAjaxRequests"; + } + + public static PluginScript INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly, + boolean initialUseOnAjaxReadyStateChange, + boolean initialUseOnAjaxProgress) { + return + new PluginScript( + InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } public static PluginScript createInterceptOnlyAsyncAjaxRequestsPluginScript(boolean onlyAsync) { return new PluginScript( InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - "window." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " = " + onlyAsync +";", + "window." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() + " = " + onlyAsync + ";", UserScriptInjectionTime.AT_DOCUMENT_START, null, true, - null + null, + false ); } - public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " = true;" + - " 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_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(new Uint8Array(request.response));" + - " return;" + - " case 'blob':" + - " const reader = new FileReader();" + - " reader.addEventListener('loadend', function() { " + - " callback(new Uint8Array(reader.result));" + - " });" + - " reader.readAsArrayBuffer(blob);" + - " 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) {" + - " var self = this;" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == 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." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxProgress', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " }" + - " });" + - " });" + - " }" + - " };" + - " ajax.prototype.send = function(data) {" + - " var self = this;" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " var canBeIntercepted = self._flutter_inappwebview_isAsync || w." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " === false;" + - " if (canBeIntercepted && (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true)) {" + - " if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {" + - " this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" + - " var onreadystatechange = this.onreadystatechange;" + - " this.onreadystatechange = function() {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == 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." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " }" + - " if (onreadystatechange != null) {" + - " onreadystatechange();" + - " }" + - " });" + - " });" + - " } else if (onreadystatechange != null) {" + - " onreadystatechange();" + - " }" + - " };" + - " }" + - " 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);" + - " " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(data).then(function(data) {" + - " var ajaxRequest = {" + - " data: data," + - " 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." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) && result.data.length > 0) {" + - " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.data);" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + - " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".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 (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) || result.data == null) {" + - " data = result.data;" + - " } else if (result.data.length > 0) {" + - " data = new Uint8Array(result.data);" + - " }" + - " self.withCredentials = result.withCredentials;" + - " if (result.responseType != null && self._flutter_inappwebview_isAsync) {" + - " self.responseType = result.responseType;" + - " };" + - " 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, data);" + - " });" + - " });" + - " } else {" + - " send.call(this, data);" + - " }" + - " };" + - "})(window.XMLHttpRequest);"; + public static String INTERCEPT_AJAX_REQUEST_JS_SOURCE(boolean initialUseOnAjaxReadyStateChange, boolean initialUseOnAjaxProgress) { + return + "(function(ajax) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " = true;" + + " w." + FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() + " = " + initialUseOnAjaxReadyStateChange + ";" + + " w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " = " + initialUseOnAjaxProgress + ";" + + " 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_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(new Uint8Array(request.response));" + + " return;" + + " case 'blob':" + + " const reader = new FileReader();" + + " reader.addEventListener('loadend', function() { " + + " callback(new Uint8Array(reader.result));" + + " });" + + " reader.readAsArrayBuffer(blob);" + + " 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) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " === false || w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " == null || w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " === false) {" + + " return;" + + " }" + + " var self = this;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == 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." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onAjaxProgress', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " }" + + " });" + + " });" + + " }" + + " };" + + " ajax.prototype.send = function(data) {" + + " var self = this;" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " var canBeIntercepted = self._flutter_inappwebview_isAsync || w." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() + " === false;" + + " if (canBeIntercepted && (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == true)) {" + + " if (w." + FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() + " === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) {" + + " this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" + + " var realOnreadystatechange = this.onreadystatechange;" + + " this.onreadystatechange = function() {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == 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." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 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);" + + " " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyRequest(data).then(function(data) {" + + " var ajaxRequest = {" + + " data: data," + + " 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." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.data) && result.data.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".arrayBufferToString(result.data);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".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 (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.data) || result.data == null) {" + + " data = result.data;" + + " } else if (result.data.length > 0) {" + + " data = new Uint8Array(result.data);" + + " }" + + " self.withCredentials = result.withCredentials;" + + " if (result.responseType != null && self._flutter_inappwebview_isAsync) {" + + " self.responseType = result.responseType;" + + " };" + + " 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, data);" + + " });" + + " });" + + " } else {" + + " send.call(this, data);" + + " }" + + " };" + + "})(window.XMLHttpRequest);"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java index eebff12cb..454717985 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java @@ -1,152 +1,166 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class InterceptFetchRequestJS { public static final String INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptFetchRequest"; - public static final PluginScript INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript( - InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); - public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " = true;" + - " if (fetch == null) {" + - " return;" + - " }" + - " window.fetch = async function(resource, init) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == 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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertHeadersToJson(fetchRequest.headers);" + - " }" + - " fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertCredentialsToJson(fetchRequest.credentials);" + - " return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(fetchRequest.body).then(function(body) {" + - " fetchRequest.body = body;" + - " return window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result.action) {" + - " case 0:" + - " var controller = new AbortController();" + - " if (init != null) {" + - " init.signal = controller.signal;" + - " } else {" + - " init = {" + - " signal: controller.signal" + - " };" + - " }" + - " controller.abort();" + - " break;" + - " }" + - " if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) && result.body.length > 0) {" + - " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.body);" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + - " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToHeaders(result.headers);" + - " }" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".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);"; + public static String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useShouldInterceptFetchRequest"; + } + + public static PluginScript INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return new PluginScript( + InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } + + public static String INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + return "(function(fetch) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " = true;" + + " if (fetch == null) {" + + " return;" + + " }" + + " window.fetch = async function(resource, init) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " == 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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertHeadersToJson(fetchRequest.headers);" + + " }" + + " fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertCredentialsToJson(fetchRequest.credentials);" + + " return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyRequest(fetchRequest.body).then(function(body) {" + + " fetchRequest.body = body;" + + " return window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result.action) {" + + " case 0:" + + " var controller = new AbortController();" + + " if (init != null) {" + + " init.signal = controller.signal;" + + " } else {" + + " init = {" + + " signal: controller.signal" + + " };" + + " }" + + " controller.abort();" + + " break;" + + " }" + + " if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.body) && result.body.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".arrayBufferToString(result.body);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertJsonToHeaders(result.headers);" + + " }" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".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 = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".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);"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java index 55659d479..a48d08a1e 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java @@ -1,250 +1,361 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.pichillilorenzo.flutter_inappwebview_android.Util; import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class JavaScriptBridgeJS { - public static final String JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + @NonNull + private static String _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + + public static void set_JAVASCRIPT_BRIDGE_NAME(@NonNull String bridgeName) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName; + } + + @NonNull + public static String get_JAVASCRIPT_BRIDGE_NAME() { + return _JAVASCRIPT_BRIDGE_NAME; + } + public static final String JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; - public static final PluginScript JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = new PluginScript( - JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); - public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util"; - public static final String WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._webMessageChannels"; + private static final String VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET"; + + public static PluginScript JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(@NonNull String expectedBridgeSecret, + @Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + String source = Util.replaceAll(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE(), VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, expectedBridgeSecret); + return new PluginScript( + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source, + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } + + public static String JAVASCRIPT_UTIL_VAR_NAME() { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + "._Util"; + } + + public static String WEB_MESSAGE_CHANNELS_VARIABLE_NAME() { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + "._webMessageChannels"; + } - public static final String UTIL_JS_SOURCE = JAVASCRIPT_UTIL_VAR_NAME + " = {" + - " support: {" + - " searchParams: 'URLSearchParams' in window," + - " iterable: 'Symbol' in window && 'iterator' in Symbol," + - " blob:" + - " 'FileReader' in window &&" + - " 'Blob' in window &&" + - " (function() {" + - " try {" + - " new Blob();" + - " return true;" + - " } catch (e) {" + - " return false;" + - " }" + - " })()," + - " formData: 'FormData' in window," + - " arrayBuffer: 'ArrayBuffer' in window" + - " }," + - " isDataView: function(obj) {" + - " return obj && DataView.prototype.isPrototypeOf(obj);" + - " }," + - " fileReaderReady: function(reader) {" + - " return new Promise(function(resolve, reject) {" + - " reader.onload = function() {" + - " resolve(reader.result);" + - " };" + - " reader.onerror = function() {" + - " reject(reader.error);" + - " };" + - " });" + - " }," + - " readBlobAsArrayBuffer: function(blob) {" + - " var reader = new FileReader();" + - " var promise = " + JAVASCRIPT_UTIL_VAR_NAME + ".fileReaderReady(reader);" + - " reader.readAsArrayBuffer(blob);" + - " return promise;" + - " }," + - " convertBodyToArrayBuffer: function(body) {" + - " var viewClasses = [" + - " '[object Int8Array]'," + - " '[object Uint8Array]'," + - " '[object Uint8ClampedArray]'," + - " '[object Int16Array]'," + - " '[object Uint16Array]'," + - " '[object Int32Array]'," + - " '[object Uint32Array]'," + - " '[object Float32Array]'," + - " '[object Float64Array]'" + - " ];" + - " var isArrayBufferView = null;" + - " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer) {" + - " isArrayBufferView =" + - " ArrayBuffer.isView ||" + - " function(obj) {" + - " return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" + - " };" + - " }" + - " var bodyUsed = false;" + - " this._bodyInit = body;" + - " if (!body) {" + - " this._bodyText = '';" + - " } else if (typeof body === 'string') {" + - " this._bodyText = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" + - " this._bodyBlob = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" + - " this._bodyFormData = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" + - " this._bodyText = body.toString();" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME + ".isDataView(body)) {" + - " this._bodyArrayBuffer = bufferClone(body.buffer);" + - " this._bodyInit = new Blob([this._bodyArrayBuffer]);" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" + - " this._bodyArrayBuffer = bufferClone(body);" + - " } else {" + - " this._bodyText = body = Object.prototype.toString.call(body);" + - " }" + - " this.blob = function () {" + - " if (bodyUsed) {" + - " return Promise.reject(new TypeError('Already read'));" + - " }" + - " bodyUsed = true;" + - " if (this._bodyBlob) {" + - " return Promise.resolve(this._bodyBlob);" + - " } else if (this._bodyArrayBuffer) {" + - " return Promise.resolve(new Blob([this._bodyArrayBuffer]));" + - " } else if (this._bodyFormData) {" + - " throw new Error('could not read FormData body as blob');" + - " } else {" + - " return Promise.resolve(new Blob([this._bodyText]));" + - " }" + - " };" + - " if (this._bodyArrayBuffer) {" + - " if (bodyUsed) {" + - " return Promise.reject(new TypeError('Already read'));" + - " }" + - " bodyUsed = true;" + - " if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" + - " return Promise.resolve(" + - " this._bodyArrayBuffer.buffer.slice(" + - " this._bodyArrayBuffer.byteOffset," + - " this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" + - " )" + - " );" + - " } else {" + - " return Promise.resolve(this._bodyArrayBuffer);" + - " }" + - " }" + - " return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME + ".readBlobAsArrayBuffer);" + - " }," + - " isString: function(variable) {" + - " return typeof variable === 'string' || variable instanceof String;" + - " }," + - " convertBodyRequest: function(body) {" + - " if (body == null) {" + - " return new Promise(function(resolve, reject) { resolve(null); });" + - " }" + - " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && body instanceof URLSearchParams)) {" + - " return new Promise(function(resolve, reject) { resolve(body.toString()); });" + - " }" + - " if (window.Response != null) {" + - " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + - " return Array.from(new Uint8Array(arrayBuffer));" + - " });" + - " }" + - " return " + JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" + - " return Array.from(new Uint8Array(arrayBuffer));" + - " });" + - " }," + - " arrayBufferToString: function(arrayBuffer) {" + - " var uint8Array = new Uint8Array(arrayBuffer);" + - " return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');" + - " }," + - " isBodyFormData: function(bodyString) {" + - " return bodyString.indexOf('------WebKitFormBoundary') >= 0;" + - " }," + - " getFormDataContentType: function(bodyString) {" + - " var boundary = bodyString.substr(2, 40);" + - " return 'multipart/form-data; boundary=' + boundary;" + - " }," + - " convertHeadersToJson: function(headers) {" + - " var headersObj = {};" + - " for (var header of headers.keys()) {" + - " var value = headers.get(header);" + - " headersObj[header] = value;" + - " }" + - " return headersObj;" + - " }," + - " convertJsonToHeaders: function(headersJson) {" + - " return new Headers(headersJson);" + - " }," + - " 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;" + - " }" + - "};"; + public static String UTIL_JS_SOURCE() { + return JAVASCRIPT_UTIL_VAR_NAME() + " = {" + + " support: {" + + " searchParams: 'URLSearchParams' in window," + + " iterable: 'Symbol' in window && 'iterator' in Symbol," + + " blob:" + + " 'FileReader' in window &&" + + " 'Blob' in window &&" + + " (function() {" + + " try {" + + " new Blob();" + + " return true;" + + " } catch (e) {" + + " return false;" + + " }" + + " })()," + + " formData: 'FormData' in window," + + " arrayBuffer: 'ArrayBuffer' in window" + + " }," + + " isDataView: function(obj) {" + + " return obj && DataView.prototype.isPrototypeOf(obj);" + + " }," + + " fileReaderReady: function(reader) {" + + " return new Promise(function(resolve, reject) {" + + " reader.onload = function() {" + + " resolve(reader.result);" + + " };" + + " reader.onerror = function() {" + + " reject(reader.error);" + + " };" + + " });" + + " }," + + " readBlobAsArrayBuffer: function(blob) {" + + " var reader = new FileReader();" + + " var promise = " + JAVASCRIPT_UTIL_VAR_NAME() + ".fileReaderReady(reader);" + + " reader.readAsArrayBuffer(blob);" + + " return promise;" + + " }," + + " convertBodyToArrayBuffer: function(body) {" + + " var viewClasses = [" + + " '[object Int8Array]'," + + " '[object Uint8Array]'," + + " '[object Uint8ClampedArray]'," + + " '[object Int16Array]'," + + " '[object Uint16Array]'," + + " '[object Int32Array]'," + + " '[object Uint32Array]'," + + " '[object Float32Array]'," + + " '[object Float64Array]'" + + " ];" + + " var isArrayBufferView = null;" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer) {" + + " isArrayBufferView =" + + " ArrayBuffer.isView ||" + + " function(obj) {" + + " return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" + + " };" + + " }" + + " var bodyUsed = false;" + + " this._bodyInit = body;" + + " if (!body) {" + + " this._bodyText = '';" + + " } else if (typeof body === 'string') {" + + " this._bodyText = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" + + " this._bodyBlob = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" + + " this._bodyFormData = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" + + " this._bodyText = body.toString();" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME() + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME() + ".isDataView(body)) {" + + " this._bodyArrayBuffer = bufferClone(body.buffer);" + + " this._bodyInit = new Blob([this._bodyArrayBuffer]);" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" + + " this._bodyArrayBuffer = bufferClone(body);" + + " } else {" + + " this._bodyText = body = Object.prototype.toString.call(body);" + + " }" + + " this.blob = function () {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (this._bodyBlob) {" + + " return Promise.resolve(this._bodyBlob);" + + " } else if (this._bodyArrayBuffer) {" + + " return Promise.resolve(new Blob([this._bodyArrayBuffer]));" + + " } else if (this._bodyFormData) {" + + " throw new Error('could not read FormData body as blob');" + + " } else {" + + " return Promise.resolve(new Blob([this._bodyText]));" + + " }" + + " };" + + " if (this._bodyArrayBuffer) {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" + + " return Promise.resolve(" + + " this._bodyArrayBuffer.buffer.slice(" + + " this._bodyArrayBuffer.byteOffset," + + " this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" + + " )" + + " );" + + " } else {" + + " return Promise.resolve(this._bodyArrayBuffer);" + + " }" + + " }" + + " return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME() + ".readBlobAsArrayBuffer);" + + " }," + + " isString: function(variable) {" + + " return typeof variable === 'string' || variable instanceof String;" + + " }," + + " convertBodyRequest: function(body) {" + + " if (body == null) {" + + " return new Promise(function(resolve, reject) { resolve(null); });" + + " }" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.searchParams && body instanceof URLSearchParams)) {" + + " return new Promise(function(resolve, reject) { resolve(body.toString()); });" + + " }" + + " if (window.Response != null) {" + + " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }" + + " return " + JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }," + + " arrayBufferToString: function(arrayBuffer) {" + + " var uint8Array = new Uint8Array(arrayBuffer);" + + " return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');" + + " }," + + " isBodyFormData: function(bodyString) {" + + " return bodyString.indexOf('------WebKitFormBoundary') >= 0;" + + " }," + + " getFormDataContentType: function(bodyString) {" + + " var boundary = bodyString.substr(2, 40);" + + " return 'multipart/form-data; boundary=' + boundary;" + + " }," + + " convertHeadersToJson: function(headers) {" + + " var headersObj = {};" + + " for (var header of headers.keys()) {" + + " var value = headers.get(header);" + + " headersObj[header] = value;" + + " }" + + " return headersObj;" + + " }," + + " convertJsonToHeaders: function(headersJson) {" + + " return new Headers(headersJson);" + + " }," + + " 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;" + + " }" + + "};"; + } - public static final String JAVASCRIPT_BRIDGE_JS_SOURCE = "if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" + - " var _callHandlerID = setTimeout(function(){});" + - " window." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" + - " return new Promise(function(resolve, reject) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" + - " });" + - " };" + - "}"+ - "if (window.top != null && window.top !== window && window." + JAVASCRIPT_BRIDGE_NAME + " == null) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + " = {};" + - " window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" + - " var _callHandlerID = setTimeout(function(){});" + - " try {" + - " window.top." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" + - " return new Promise(function(resolve, reject) {" + - " window.top." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" + - " });" + - " } catch (error) {" + - " return new Promise(function(resolve, reject) { reject(error); });" + - " }" + - " };" + - "}" + - "if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" + - " " + UTIL_JS_SOURCE + - "}"; + public static String JAVASCRIPT_BRIDGE_JS_SOURCE() { + return "if (window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null) {" + + " (function(window) {" + + " var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';" + + " var origin = '';" + + " var requestUrl = '';" + + " var isMainFrame = false;" + + " var _JSON_stringify;" + + " var _Array_slice;" + + " var _setTimeout;" + + " var _Promise;" + + " var _javaInjectedObject;" + + " try {" + + " origin = window.location.origin;" + + " } catch (_) {}" + + " try {" + + " requestUrl = window.location.href;" + + " } catch (_) {}" + + " try {" + + " isMainFrame = window === window.top;" + + " } catch (_) {}" + + " try {" + + " _JSON_stringify = window.JSON.stringify;" + + " _Array_slice = window.Array.prototype.slice;" + + " _Array_slice.call = window.Function.prototype.call;" + + " _setTimeout = window.setTimeout;" + + " _Promise = window.Promise;" + + " _javaInjectedObject = window." + get_JAVASCRIPT_BRIDGE_NAME() + ";" + + " } catch (_) { return; }" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() {" + + " var _callHandlerID = _setTimeout(function(){});" + + " _javaInjectedObject._callHandler(_JSON_stringify({" + + " 'handlerName': arguments[0]," + + " '_callHandlerID': _callHandlerID," + + " '_bridgeSecret': bridgeSecret," + + " 'origin': origin," + + " 'requestUrl': requestUrl," + + " 'isMainFrame': isMainFrame," + + " 'args': _JSON_stringify(_Array_slice.call(arguments, 1))" + + " }));" + + " return new _Promise(function(resolve, reject) {" + + " try {" + + " (isMainFrame ? window : window.top)." + get_JAVASCRIPT_BRIDGE_NAME() + "[_callHandlerID] = {resolve: resolve, reject: reject};" + + " } catch(e) { resolve(); }" + + " });" + + " };" + + " })(window);" + + "}" + + "if (window.top != null && window.top !== window && window." + get_JAVASCRIPT_BRIDGE_NAME() + " == null) {" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + " = {};" + + " (function(window) {" + + " var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';" + + " var origin = '';" + + " var requestUrl = '';" + + " var isMainFrame = false;" + + " var _JSON_stringify;" + + " var _Array_slice;" + + " var _setTimeout;" + + " var _Promise;" + + " var _javaInjectedObject;" + + " try {" + + " origin = window.location.origin;" + + " } catch (_) {}" + + " try {" + + " requestUrl = window.location.href;" + + " } catch (_) {}" + + " try {" + + " isMainFrame = window === window.top;" + + " } catch (_) {}" + + " try {" + + " _JSON_stringify = window.JSON.stringify;" + + " _Array_slice = window.Array.prototype.slice;" + + " _Array_slice.call = window.Function.prototype.call;" + + " _setTimeout = window.setTimeout;" + + " _Promise = window.Promise;" + + " _javaInjectedObject = window.top." + get_JAVASCRIPT_BRIDGE_NAME() + ";" + + " } catch (_) { return; }" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() {" + + " var _callHandlerID = _setTimeout(function(){});" + + " try {" + + " _javaInjectedObject._callHandler(_JSON_stringify({" + + " 'handlerName': arguments[0]," + + " '_callHandlerID': _callHandlerID," + + " '_bridgeSecret': bridgeSecret," + + " 'origin': origin," + + " 'requestUrl': requestUrl," + + " 'isMainFrame': isMainFrame," + + " 'args': _JSON_stringify(_Array_slice.call(arguments, 1))" + + " }));" + + " return new _Promise(function(resolve, reject) {" + + " _javaInjectedObject[_callHandlerID] = {resolve: resolve, reject: reject};" + + " });" + + " } catch (error) {" + + " return new _Promise(function(resolve, reject) { resolve(); });" + + " }" + + " };" + + " })(window);" + + "}" + + "if (window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null) {" + + " " + UTIL_JS_SOURCE() + + "}"; + } - public static final String PLATFORM_READY_JS_SOURCE = "(function() {" + - " if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + " != null && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) {" + - " window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" + - " window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady = true;" + - " }" + - "})();"; + public static String PLATFORM_READY_JS_SOURCE() { + return "(function() {" + + " if ((window.top == null || window.top === window) && window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null && window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady == null) {" + + " window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady = true;" + + " }" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java index b386bd7de..fdc512a50 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java @@ -1,35 +1,50 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnLoadResourceJS { public static final String ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useOnLoadResource"; - public static final PluginScript ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = new PluginScript( - OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static String FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnLoadResource"; + } + public static PluginScript ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } - public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" + - "(function() {" + - " var observer = new PerformanceObserver(function(list) {" + - " list.getEntries().forEach(function(entry) {" + - " if (" + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == null || " + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == true) {" + - " var resource = {" + - " 'url': entry.name," + - " 'initiatorType': entry.initiatorType," + - " 'startTime': entry.startTime," + - " 'duration': entry.duration" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onLoadResource', resource);" + - " }" + - " });" + - " });" + - " observer.observe({entryTypes: ['resource']});" + - "})();"; + public static String ON_LOAD_RESOURCE_JS_SOURCE() { + return + "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " = true;" + + "(function() {" + + " var observer = new PerformanceObserver(function(list) {" + + " list.getEntries().forEach(function(entry) {" + + " if (" + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " == null || " + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " == true) {" + + " var resource = {" + + " 'url': entry.name," + + " 'initiatorType': entry.initiatorType," + + " 'startTime': entry.startTime," + + " 'duration': entry.duration" + + " };" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onLoadResource', resource);" + + " }" + + " });" + + " });" + + " observer.observe({entryTypes: ['resource']});" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java index 746f8782a..ef2ba8c37 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java @@ -1,22 +1,35 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnWindowBlurEventJS { public static final String ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT"; - public static final PluginScript ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = new PluginScript( - OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); - public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" + - " window.addEventListener('blur', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowBlur');" + - " });" + - "})();"; + // This plugin is only for main frame + public static PluginScript ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules) { + return + new PluginScript( + OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + true + ); + } + + public static String ON_WINDOW_BLUR_EVENT_JS_SOURCE() { + return + "(function(){" + + " window.addEventListener('blur', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWindowBlur');" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java index cfdae9a26..bf2e9a454 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java @@ -1,22 +1,35 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnWindowFocusEventJS { public static final String ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT"; - public static final PluginScript ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = new PluginScript( - OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); - public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" + - " window.addEventListener('focus', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowFocus');" + - " });" + - "})();"; + // This plugin is only for main frame + public static PluginScript ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules) { + return + new PluginScript( + OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + true + ); + } + + public static String ON_WINDOW_FOCUS_EVENT_JS_SOURCE() { + return + "(function(){" + + " window.addEventListener('focus', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWindowFocus');" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java index 91af1fdd1..de6c271d1 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java @@ -1,8 +1,12 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PluginScriptsUtil { public static final String VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"; @@ -16,24 +20,30 @@ public class PluginScriptsUtil { public static final String VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID"; public static final String VAR_RANDOM_NAME = "$IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME"; - public static final String CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE = "(function(obj) {" + - " (async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") {" + - " \n" + VAR_FUNCTION_BODY + "\n" + - " })(" + VAR_FUNCTION_ARGUMENT_VALUES + ").then(function(value) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '" + VAR_RESULT_UUID + "'});" + - " }).catch(function(error) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error + '', 'resultUuid': '" + VAR_RESULT_UUID + "'});" + - " });" + - " return null;" + - "})(" + VAR_FUNCTION_ARGUMENTS_OBJ + ");"; + public static String CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE() { + return + "(function(obj) {" + + " (async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") {" + + " \n" + VAR_FUNCTION_BODY + "\n" + + " })(" + VAR_FUNCTION_ARGUMENT_VALUES + ").then(function(value) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '" + VAR_RESULT_UUID + "'});" + + " }).catch(function(error) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error + '', 'resultUuid': '" + VAR_RESULT_UUID + "'});" + + " });" + + " return null;" + + "})(" + VAR_FUNCTION_ARGUMENTS_OBJ + ");"; + } - public static final String EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE = "var $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = null;" + - "try {" + - " $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = eval(" + VAR_PLACEHOLDER_VALUE + ");" + - "} catch(e) {" + - " console.error(e);" + - "}" + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('evaluateJavaScriptWithContentWorld', {'value': $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME, 'resultUuid': '" + VAR_RESULT_UUID + "'});"; + public static String EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE() { + return + "var $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = null;" + + "try {" + + " $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = eval(" + VAR_PLACEHOLDER_VALUE + ");" + + "} catch(e) {" + + " console.error(e);" + + "}" + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('evaluateJavaScriptWithContentWorld', {'value': $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME, 'resultUuid': '" + VAR_RESULT_UUID + "'});"; + } public static final String IS_ACTIVE_ELEMENT_INPUT_EDITABLE_JS_SOURCE = "var activeEl = document.activeElement;" + @@ -71,19 +81,27 @@ public class PluginScriptsUtil { "})();"; public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME = "CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT"; - public static final PluginScript CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT = new PluginScript( - PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME, - PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static PluginScript CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME, + PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } // android Workaround to hide context menu when user emit a keydown event - public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE = "(function(){" + - " document.addEventListener('keydown', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._hideContextMenu();" + - " });" + - "})();"; + public static String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE() { + return + "(function(){" + + " document.addEventListener('keydown', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._hideContextMenu();" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java index bb1084815..dc273457f 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java @@ -1,24 +1,36 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PrintJS { public static final String PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT"; - public static final PluginScript PRINT_JS_PLUGIN_SCRIPT = new PluginScript( - PrintJS.PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - PrintJS.PRINT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static PluginScript PRINT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + PrintJS.PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + PrintJS.PRINT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } - public static final String PRINT_JS_SOURCE = "window.print = function() {" + - " if (window.top == null || window.top === window) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onPrintRequest', window.location.href);" + - " } else {" + - " window.top.print();" + - " }" + - "};"; + public static String PRINT_JS_SOURCE() { + return + "window.print = function() {" + + " if (window.top == null || window.top === window) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onPrintRequest', window.location.href);" + + " } else {" + + " window.top.print();" + + " }" + + "};"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java index af10c5d84..3331c63a0 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java @@ -1,18 +1,26 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PromisePolyfillJS { public static final String PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT"; - public static final PluginScript PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = new PluginScript( - PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); + public static final PluginScript PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return new PluginScript( + PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } // https://github.com/tildeio/rsvp.js public static final String PROMISE_POLYFILL_JS_SOURCE = "if (window.Promise == null) {" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java index 1a2165670..28364aca7 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java @@ -9,6 +9,9 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview_android.types.PrintJobInfoExt; +import java.util.HashMap; +import java.util.Map; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -62,6 +65,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result } } + public void onComplete(boolean completed, @Nullable String error) { + MethodChannel channel = getChannel(); + if (channel == null) return; + Map obj = new HashMap<>(); + obj.put("completed", completed); + obj.put("error", error); + channel.invokeMethod("onComplete", obj); + } + @Override public void dispose() { super.dispose(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java index 2283e44f6..6d962e29c 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java @@ -28,16 +28,19 @@ public class PrintJobController implements Disposable { @Nullable public PrintJobSettings settings; - public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job, - @Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) { + public PrintJobController(@NonNull String id, @Nullable PrintJobSettings settings, + @NonNull InAppWebViewFlutterPlugin plugin) { this.id = id; this.plugin = plugin; - this.job = job; this.settings = settings; final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); this.channelDelegate = new PrintJobChannelDelegate(this, channel); } - + + public void setJob(@Nullable android.print.PrintJob job) { + this.job = job; + } + public void cancel() { if (this.job != null) { this.job.cancel(); @@ -93,4 +96,8 @@ public void dispose() { } plugin = null; } + + public void onComplete(boolean completed, @Nullable String error) { + if (channelDelegate != null) channelDelegate.onComplete(completed, error); + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java index dfbc0bfd4..3f6367329 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java @@ -40,6 +40,8 @@ public PullToRefreshLayout(@NonNull Context context, @Nullable AttributeSet attr } public void prepare() { + setFocusable(true); + final PullToRefreshLayout self = this; setEnabled(settings.enabled); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java new file mode 100644 index 000000000..b297899ff --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java @@ -0,0 +1,107 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import android.graphics.Rect; + +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class InAppWebViewRect { + private double height; + private double width; + private double x; + private double y; + + public InAppWebViewRect(double height, double width, double x, double y) { + this.height = height; + this.width = width; + this.x = x; + this.y = y; + } + + @Nullable + public static InAppWebViewRect fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + double height = (double) map.get("height"); + double width = (double) map.get("width"); + double x = (double) map.get("x"); + double y = (double) map.get("y"); + return new InAppWebViewRect(height, width, x, y); + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("height", height); + map.put("width", width); + map.put("x", x); + map.put("y", y); + return map; + } + + public Rect toRect() { + return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height)); + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InAppWebViewRect that = (InAppWebViewRect) o; + return Double.compare(height, that.height) == 0 && Double.compare(width, that.width) == 0 && Double.compare(x, that.x) == 0 && Double.compare(y, that.y) == 0; + } + + @Override + public int hashCode() { + int result = Double.hashCode(height); + result = 31 * result + Double.hashCode(width); + result = 31 * result + Double.hashCode(x); + result = 31 * result + Double.hashCode(y); + return result; + } + + @Override + public String toString() { + return "InAppWebViewRect{" + + "height=" + height + + ", width=" + width + + ", x=" + x + + ", y=" + y + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java new file mode 100644 index 000000000..8cf6aa294 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java @@ -0,0 +1,108 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class JavaScriptHandlerFunctionData { + @NonNull + private String origin; + @NonNull + private String requestUrl; + private boolean isMainFrame; + @NonNull + private String args; + + public JavaScriptHandlerFunctionData(@NonNull String origin, @NonNull String requestUrl, boolean isMainFrame, @NonNull String args) { + this.origin = origin; + this.requestUrl = requestUrl; + this.isMainFrame = isMainFrame; + this.args = args; + } + + @Nullable + public static JavaScriptHandlerFunctionData fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + String origin = (String) map.get("origin"); + String requestUrl = (String) map.get("requestUrl"); + boolean isMainFrame = (boolean) map.get("isMainFrame"); + String args = (String) map.get("args"); + return new JavaScriptHandlerFunctionData(origin, requestUrl, isMainFrame, args); + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("origin", origin); + map.put("requestUrl", requestUrl); + map.put("isMainFrame", isMainFrame); + map.put("args", args); + return map; + } + + @NonNull + public String getRequestUrl() { + return requestUrl; + } + + public void setRequestUrl(@NonNull String requestUrl) { + this.requestUrl = requestUrl; + } + + @NonNull + public String getOrigin() { + return origin; + } + + public void setOrigin(@NonNull String origin) { + this.origin = origin; + } + + public boolean isMainFrame() { + return isMainFrame; + } + + public void setMainFrame(boolean mainFrame) { + isMainFrame = mainFrame; + } + + @NonNull + public String getArgs() { + return args; + } + + public void setArgs(@NonNull String args) { + this.args = args; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaScriptHandlerFunctionData that = (JavaScriptHandlerFunctionData) o; + return isMainFrame == that.isMainFrame && origin.equals(that.origin) && requestUrl.equals(that.requestUrl) && args.equals(that.args); + } + + @Override + public int hashCode() { + int result = origin.hashCode(); + result = 31 * result + requestUrl.hashCode(); + result = 31 * result + Boolean.hashCode(isMainFrame); + result = 31 * result + args.hashCode(); + return result; + } + + @Override + public String toString() { + return "JavaScriptHandlerFunctionData{" + + "origin='" + origin + '\'' + + ", requestUrl='" + requestUrl + '\'' + + ", isMainFrame=" + isMainFrame + + ", args='" + args + '\'' + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java index b16f22803..a5a95e4ea 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java @@ -8,8 +8,9 @@ public class PluginScript extends UserScript { private boolean requiredInAllContentWorlds; - public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set allowedOriginRules) { - super(groupName, source, injectionTime, contentWorld, allowedOriginRules); + public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, + @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set allowedOriginRules, boolean forMainFrameOnly) { + super(groupName, source, injectionTime, contentWorld, allowedOriginRules, forMainFrameOnly); this.requiredInAllContentWorlds = requiredInAllContentWorlds; } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java new file mode 100644 index 000000000..eb26459b8 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java @@ -0,0 +1,140 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import android.annotation.TargetApi; +import android.os.Build; +import android.webkit.WebChromeClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserRequest { + private int mode; + @NonNull + private List acceptTypes; + private boolean isCaptureEnabled; + @Nullable + private String title; + @Nullable + private String filenameHint; + + public ShowFileChooserRequest(int mode, @NonNull List acceptTypes, boolean isCaptureEnabled, @Nullable String title, @Nullable String filenameHint) { + this.mode = mode; + this.acceptTypes = acceptTypes; + this.isCaptureEnabled = isCaptureEnabled; + this.title = title; + this.filenameHint = filenameHint; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static ShowFileChooserRequest fromFileChooserParams(WebChromeClient.FileChooserParams fileChooserParams) { + int mode = fileChooserParams.getMode(); + List acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes()); + boolean isCaptureEnabled = fileChooserParams.isCaptureEnabled(); + String title = fileChooserParams.getTitle() != null ? fileChooserParams.getTitle().toString() : null; + String filenameHint = fileChooserParams.getFilenameHint(); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + @Nullable + public static ShowFileChooserRequest fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + int mode = (int) map.get("mode"); + List acceptTypes = (List) map.get("acceptTypes"); + boolean isCaptureEnabled = (boolean) map.get("isCaptureEnabled"); + String title = (String) map.get("title"); + String filenameHint = (String) map.get("filenameHint"); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + public Map toMap() { + Map showFileChooserRequestMap = new HashMap<>(); + showFileChooserRequestMap.put("mode", mode); + showFileChooserRequestMap.put("acceptTypes", acceptTypes); + showFileChooserRequestMap.put("isCaptureEnabled", isCaptureEnabled); + showFileChooserRequestMap.put("title", title); + showFileChooserRequestMap.put("filenameHint", filenameHint); + return showFileChooserRequestMap; + } + + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public @NonNull List getAcceptTypes() { + return acceptTypes; + } + + public void setAcceptTypes(@NonNull List acceptTypes) { + this.acceptTypes = acceptTypes; + } + + public boolean isCaptureEnabled() { + return isCaptureEnabled; + } + + public void setCaptureEnabled(boolean captureEnabled) { + isCaptureEnabled = captureEnabled; + } + + @Nullable + public String getTitle() { + return title; + } + + public void setTitle(@Nullable String title) { + this.title = title; + } + + @Nullable + public String getFilenameHint() { + return filenameHint; + } + + public void setFilenameHint(@Nullable String filenameHint) { + this.filenameHint = filenameHint; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserRequest that = (ShowFileChooserRequest) o; + return mode == that.mode && isCaptureEnabled == that.isCaptureEnabled && acceptTypes.equals(that.acceptTypes) && Objects.equals(title, that.title) && Objects.equals(filenameHint, that.filenameHint); + } + + @Override + public int hashCode() { + int result = mode; + result = 31 * result + acceptTypes.hashCode(); + result = 31 * result + Boolean.hashCode(isCaptureEnabled); + result = 31 * result + Objects.hashCode(title); + result = 31 * result + Objects.hashCode(filenameHint); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserRequest{" + + "mode=" + mode + + ", acceptTypes=" + acceptTypes + + ", isCaptureEnabled=" + isCaptureEnabled + + ", title='" + title + '\'' + + ", filenameHint='" + filenameHint + '\'' + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java new file mode 100644 index 000000000..d7cc839ce --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java @@ -0,0 +1,71 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserResponse { + private boolean handledByClient; + @Nullable + private List filePaths; + + public ShowFileChooserResponse(boolean handledByClient, @Nullable List filePaths) { + this.handledByClient = handledByClient; + this.filePaths = filePaths; + } + + @Nullable + public static ShowFileChooserResponse fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + boolean handledByClient = (boolean) map.get("handledByClient"); + List filePaths = (List) map.get("filePaths"); + return new ShowFileChooserResponse(handledByClient, filePaths); + } + + public boolean isHandledByClient() { + return handledByClient; + } + + public void setHandledByClient(boolean handledByClient) { + this.handledByClient = handledByClient; + } + + @Nullable + public List getFilePaths() { + return filePaths; + } + + public void setFilePaths(@Nullable List filePaths) { + this.filePaths = filePaths; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserResponse that = (ShowFileChooserResponse) o; + return handledByClient == that.handledByClient && Objects.equals(filePaths, that.filePaths); + } + + @Override + public int hashCode() { + int result = Boolean.hashCode(handledByClient); + result = 31 * result + Objects.hashCode(filePaths); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserResponse{" + + "handledByClient=" + handledByClient + + ", filePaths=" + filePaths + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java index f51dd1b93..0e59a581d 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java @@ -53,7 +53,7 @@ public class UserContentController implements Disposable { @Nullable public WebView webView; - public UserContentController(WebView webView) { + public UserContentController(@Nullable WebView webView) { this.webView = webView; } @@ -73,7 +73,7 @@ public String generateWrappedCodeForDocumentEnd() { } js += generatePluginScriptsCodeAt(injectionTime); js += generateUserOnlyScriptsCodeAt(injectionTime); - js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); + js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE().replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); return js; } @@ -83,7 +83,7 @@ public String generateCodeForDocumentStart() { js += generatePluginScriptsCodeAt(injectionTime); js += generateContentWorldsCreatorCode(); js += generateUserOnlyScriptsCodeAt(injectionTime); - js = USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); + js = USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE().replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); return js; } @@ -105,7 +105,7 @@ public String generateContentWorldsCreatorCode() { contentWorldsNames.add("'" + escapeContentWorldName(contentWorld.getName()) + "'"); } - return CONTENT_WORLDS_GENERATOR_JS_SOURCE + return CONTENT_WORLDS_GENERATOR_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY, TextUtils.join(", ", contentWorldsNames)) .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(source.toString())); @@ -117,6 +117,7 @@ public String generatePluginScriptsCodeAt(UserScriptInjectionTime injectionTime) for (PluginScript script : scripts) { String source = ";" + script.getSource(); source = wrapSourceCodeInContentWorld(script.getContentWorld(), source); + source = wrapSourceCodeAddChecks(source, script); js.append(source); } return js.toString(); @@ -128,6 +129,7 @@ public String generateUserOnlyScriptsCodeAt(UserScriptInjectionTime injectionTim for (UserScript script : scripts) { String source = ";" + script.getSource(); source = wrapSourceCodeInContentWorld(script.getContentWorld(), source); + source = wrapSourceCodeAddChecks(source, script); js.append(source); } return js.toString(); @@ -144,7 +146,7 @@ public String generateCodeForScriptEvaluation(String source, @Nullable ContentWo for (PluginScript script : pluginScriptsRequired) { pluginScriptsSource.append(script.getSource()); } - String contentWorldCreatorCode = CONTENT_WORLDS_GENERATOR_JS_SOURCE + String contentWorldCreatorCode = CONTENT_WORLDS_GENERATOR_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY, "'" + escapeContentWorldName(contentWorld.getName()) + "'") .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(pluginScriptsSource.toString())); sourceWrapped.append(contentWorldCreatorCode).append(";"); @@ -156,7 +158,7 @@ public String generateCodeForScriptEvaluation(String source, @Nullable ContentWo public String wrapSourceCodeInContentWorld(@Nullable ContentWorld contentWorld, String source) { String sourceWrapped = contentWorld == null || contentWorld.equals(ContentWorld.PAGE) ? source : - CONTENT_WORLD_WRAPPER_JS_SOURCE + CONTENT_WORLD_WRAPPER_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME, escapeContentWorldName(contentWorld.getName())) .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(source)); @@ -201,11 +203,16 @@ public boolean addUserOnlyScript(UserScript userOnlyScript) { contentWorlds.add(contentWorld); } this.updateContentWorldsCreatorScript(); - if (webView != null && userOnlyScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_START - && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = userOnlyScript.getSource(); + if (userOnlyScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_END) { + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; + } + source = wrapSourceCodeAddChecks(source, userOnlyScript); + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( webView, - wrapSourceCodeInContentWorld(userOnlyScript.getContentWorld(), userOnlyScript.getSource()), + wrapSourceCodeInContentWorld(userOnlyScript.getContentWorld(), source), userOnlyScript.getAllowedOriginRules() ); this.scriptHandlerMap.put(userOnlyScript, scriptHandler); @@ -245,6 +252,13 @@ public void removeAllUserOnlyScripts() { this.scriptHandlerMap.remove(userOnlyScript); } } + for (UserScript userOnlyScript : this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(userOnlyScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(userOnlyScript); + } + } } this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); @@ -271,11 +285,16 @@ public boolean addPluginScript(PluginScript pluginScript) { contentWorlds.add(contentWorld); } this.updateContentWorldsCreatorScript(); - if (webView != null && pluginScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_START - && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = pluginScript.getSource(); + if (pluginScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_END) { + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; + } + source = wrapSourceCodeAddChecks(source, pluginScript); + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( webView, - wrapSourceCodeInContentWorld(pluginScript.getContentWorld(), pluginScript.getSource()), + wrapSourceCodeInContentWorld(pluginScript.getContentWorld(), source), pluginScript.getAllowedOriginRules() ); this.scriptHandlerMap.put(pluginScript, scriptHandler); @@ -310,6 +329,13 @@ public void removeAllPluginScripts() { this.scriptHandlerMap.remove(pluginScript); } } + for (PluginScript pluginScript : this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(pluginScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(pluginScript); + } + } } this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); @@ -402,60 +428,99 @@ public LinkedHashSet getContentWorlds() { return new LinkedHashSet<>(this.contentWorlds); } - private static final String USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded == null || !window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded)) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded = true;" + - " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + - "}"; + static private String wrapSourceCodeAddChecks(String source, UserScript userScript) { + StringBuilder ifStatement = new StringBuilder("if ("); + Set allowedOriginRules = userScript.getAllowedOriginRules(); + boolean forMainFrameOnly = userScript.isForMainFrameOnly(); + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT) && !allowedOriginRules.contains("*")) { + if (allowedOriginRules.isEmpty()) { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return ""; + } + StringBuilder jsRegExpArray = new StringBuilder("["); + for (String allowedOriginRule : allowedOriginRules) { + if (jsRegExpArray.length() > 1) { + jsRegExpArray.append(", "); + } + jsRegExpArray.append("new RegExp(").append(UserContentController.escapeCode(allowedOriginRule)).append(")"); + } + if (jsRegExpArray.length() > 1) { + jsRegExpArray.append("]"); + ifStatement.append(jsRegExpArray).append(".some(function(rx) { return rx.test(window.location.origin); })"); + } + } + if (forMainFrameOnly) { + if (ifStatement.length() > 4) { + ifStatement.append(" && "); + } + ifStatement.append("window === window.top"); + } + return ifStatement.length() > 4 ? ifStatement.append(") {").append(source).append("}").toString() : source; + } - private static final String USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded == null || !window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded)) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded = true;" + - " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + - "}"; + private static String USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE() { + return "if (window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded == null || !window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded) {" + + " window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded = true;" + + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + + "}"; + } + + private static String USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE() { + return "if (window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded == null || !window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded) {" + + " window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded = true;" + + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + + "}"; + } + + private static String CONTENT_WORLDS_GENERATOR_JS_SOURCE() { + return "(function() {" + + " var interval = setInterval(function() {" + + " if (document.body == null) {return;}" + + " var contentWorldNames = [" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY + "];" + + " for (var contentWorldName of contentWorldNames) {" + + " var iframeId = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_' + contentWorldName;" + + " var iframe = document.getElementById(iframeId);" + + " if (iframe == null) {" + + " iframe = document.createElement('iframe');" + + " iframe.id = iframeId;" + + " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + + " document.body.append(iframe);" + + " }" + + " if (iframe.contentWindow.document.getElementById('" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts') == null) {" + + " var script = iframe.contentWindow.document.createElement('script');" + + " script.id = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts';" + + " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + + " iframe.contentWindow.document.body.append(script);" + + " }" + + " }" + + " clearInterval(interval);" + + " });" + + "})();"; + } - private static final String CONTENT_WORLDS_GENERATOR_JS_SOURCE = "(function() {" + - " var interval = setInterval(function() {" + - " if (document.body == null) {return;}" + - " var contentWorldNames = [" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY + "];" + - " for (var contentWorldName of contentWorldNames) {" + - " var iframeId = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_' + contentWorldName;" + - " var iframe = document.getElementById(iframeId);" + - " if (iframe == null) {" + - " iframe = document.createElement('iframe');" + - " iframe.id = iframeId;" + - " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + - " document.body.append(iframe);" + - " }" + - " if (iframe.contentWindow.document.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts') == null) {" + - " var script = iframe.contentWindow.document.createElement('script');" + - " script.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts';" + - " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + - " iframe.contentWindow.document.body.append(script);" + - " }" + - " }" + - " clearInterval(interval);" + - " });" + - "})();"; - - private static final String CONTENT_WORLD_WRAPPER_JS_SOURCE = "(function() {" + - " var interval = setInterval(function() {" + - " if (document.body == null) {return;}" + - " var iframeId = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME + "';" + - " var iframe = document.getElementById(iframeId);" + - " if (iframe == null) {" + - " iframe = document.createElement('iframe');" + - " iframe.id = iframeId;" + - " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + - " document.body.append(iframe);" + - " }" + - " if (iframe.contentWindow.document.querySelector('#" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts') == null) {" + - " return;" + - " }" + - " var script = iframe.contentWindow.document.createElement('script');" + - " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + - " iframe.contentWindow.document.body.append(script);" + - " clearInterval(interval);" + - " });" + - "})();"; + private static String CONTENT_WORLD_WRAPPER_JS_SOURCE() { + return "(function() {" + + " var interval = setInterval(function() {" + + " if (document.body == null) {return;}" + + " var iframeId = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME + "';" + + " var iframe = document.getElementById(iframeId);" + + " if (iframe == null) {" + + " iframe = document.createElement('iframe');" + + " iframe.id = iframeId;" + + " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + + " document.body.append(iframe);" + + " }" + + " if (iframe.contentWindow.document.querySelector('#" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts') == null) {" + + " return;" + + " }" + + " var script = iframe.contentWindow.document.createElement('script');" + + " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + + " iframe.contentWindow.document.body.append(script);" + + " clearInterval(interval);" + + " });" + + "})();"; + } private static final String DOCUMENT_READY_WRAPPER_JS_SOURCE = "if (document.readyState === 'interactive' || document.readyState === 'complete') { " + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java index 1f8e5604b..d440984b2 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java @@ -2,10 +2,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.webkit.WebViewFeature; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; public class UserScript { @@ -19,10 +21,11 @@ public class UserScript { private ContentWorld contentWorld; @NonNull private Set allowedOriginRules = new HashSet<>(); + private boolean forMainFrameOnly = true; public UserScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, - @Nullable Set allowedOriginRules) { + @Nullable Set allowedOriginRules, boolean forMainFrameOnly) { this.groupName = groupName; this.source = source; this.injectionTime = injectionTime; @@ -30,6 +33,7 @@ public UserScript(@Nullable String groupName, @NonNull String source, this.allowedOriginRules = allowedOriginRules == null ? new HashSet() {{ add("*"); }} : allowedOriginRules; + this.forMainFrameOnly = forMainFrameOnly; } @Nullable @@ -42,8 +46,9 @@ public static UserScript fromMap(@Nullable Map map) { UserScriptInjectionTime injectionTime = UserScriptInjectionTime.fromValue((int) map.get("injectionTime")); ContentWorld contentWorld = ContentWorld.fromMap((Map) map.get("contentWorld")); Set allowedOriginRules = new HashSet<>((List) map.get("allowedOriginRules")); + boolean forMainFrameOnly = (boolean) map.get("forMainFrameOnly"); assert source != null; - return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules); + return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules, forMainFrameOnly); } @Nullable @@ -91,28 +96,31 @@ public void setAllowedOriginRules(@NonNull Set allowedOriginRules) { this.allowedOriginRules = allowedOriginRules; } + public boolean isForMainFrameOnly() { + return forMainFrameOnly; + } + + public void setForMainFrameOnly(boolean forMainFrameOnly) { + this.forMainFrameOnly = forMainFrameOnly; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserScript that = (UserScript) o; - - if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) - return false; - if (!source.equals(that.source)) return false; - if (injectionTime != that.injectionTime) return false; - if (!contentWorld.equals(that.contentWorld)) return false; - return allowedOriginRules.equals(that.allowedOriginRules); + return forMainFrameOnly == that.forMainFrameOnly && Objects.equals(groupName, that.groupName) && source.equals(that.source) && injectionTime == that.injectionTime && contentWorld.equals(that.contentWorld) && allowedOriginRules.equals(that.allowedOriginRules); } @Override public int hashCode() { - int result = groupName != null ? groupName.hashCode() : 0; + int result = Objects.hashCode(groupName); result = 31 * result + source.hashCode(); result = 31 * result + injectionTime.hashCode(); result = 31 * result + contentWorld.hashCode(); result = 31 * result + allowedOriginRules.hashCode(); + result = 31 * result + Boolean.hashCode(forMainFrameOnly); return result; } @@ -124,6 +132,7 @@ public String toString() { ", injectionTime=" + injectionTime + ", contentWorld=" + contentWorld + ", allowedOriginRules=" + allowedOriginRules + + ", forMainFrameOnly=" + forMainFrameOnly + '}'; } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java index a9b6cdf2e..be9003810 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java @@ -36,10 +36,10 @@ public void setWebMessageCallback(final ValueCallback callback) throws Exc if (webView != null) { int index = name.equals("port1") ? 0 : 1; webView.evaluateJavascript("(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".onmessage = function (event) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWebMessagePortMessageReceived', {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWebMessagePortMessageReceived', {" + " 'webMessageChannelId': '" + webMessageChannel.id + "'," + " 'index': " + index + "," + " 'message': event.data" + @@ -82,13 +82,13 @@ public void postMessage(WebMessage message, final ValueCallback callback) throw new Exception("Port is already closed or transferred"); } port.isTransferred = true; - portArrayString.add(JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "']." + port.name); + portArrayString.add(JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "']." + port.name); } portsString = "[" + TextUtils.join(", ", portArrayString) + "]"; } String data = message.data != null ? Util.replaceAll(message.data, "\'", "\\'") : "null"; String source = "(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".postMessage('" + data + "', " + portsString + ");" + " }" + @@ -113,7 +113,7 @@ public void close(final ValueCallback callback) throws Exception { InAppWebViewInterface webView = webMessageChannel != null && webMessageChannel.webView != null ? webMessageChannel.webView : null; if (webView != null) { String source = "(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".close();" + " }" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java index ece312921..4c533c0c2 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java @@ -52,7 +52,8 @@ void loadDataWithBaseURL(String baseUrl, String data, boolean isLoading(); void takeScreenshot(Map screenshotConfiguration, MethodChannel.Result result); void setSettings(InAppWebViewSettings newSettings, HashMap newSettingsMap); - Map getCustomSettings(); + InAppWebViewSettings getCustomSettings(); + Map getCustomSettingsMap(); HashMap getCopyBackForwardList(); void clearAllCache(); void clearSslPreferences(); @@ -116,4 +117,9 @@ void loadDataWithBaseURL(String baseUrl, String data, @Nullable WebViewChannelDelegate getChannelDelegate(); void setChannelDelegate(@Nullable WebViewChannelDelegate eventWebViewChannelDelegate); + void showInputMethod(); + void hideInputMethod(); + @Nullable + byte[] saveState(); + boolean restoreState(byte[] state); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java index 623354aef..6b909df39 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java @@ -16,6 +16,7 @@ import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.FlutterWebView; @@ -32,7 +33,7 @@ public class InAppWebViewManager extends ChannelDelegateImpl { protected static final String LOG_TAG = "InAppWebViewManager"; public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"; - + @Nullable public InAppWebViewFlutterPlugin plugin; @@ -162,6 +163,23 @@ public void onReceiveValue(Boolean value) { } result.success(true); break; + case "enableSlowWholeDocumentDraw": + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + WebView.enableSlowWholeDocumentDraw(); + } + } + result.success(true); + break; + case "setJavaScriptBridgeName": + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME((String) call.argument("bridgeName")); + result.success(true); + break; + case "getJavaScriptBridgeName": + { + result.success(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); + } + break; default: result.notImplemented(); } @@ -207,9 +225,11 @@ public void dispose() { super.dispose(); Collection flutterWebViews = keepAliveWebViews.values(); for (FlutterWebView flutterWebView : flutterWebViews) { - String keepAliveId = flutterWebView.keepAliveId; - if (keepAliveId != null) { - disposeKeepAlive(flutterWebView.keepAliveId); + if (flutterWebView != null) { + String keepAliveId = flutterWebView.keepAliveId; + if (keepAliveId != null) { + disposeKeepAlive(keepAliveId); + } } } keepAliveWebViews.clear(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java index 615a1f86e..d24f89d62 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java @@ -9,21 +9,27 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobController; import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobSettings; +import com.pichillilorenzo.flutter_inappwebview_android.types.JavaScriptHandlerFunctionData; import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView; -import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.regex.Pattern; + public class JavaScriptBridgeInterface { private static final String LOG_TAG = "JSBridgeInterface"; private InAppWebView inAppWebView; - - public JavaScriptBridgeInterface(InAppWebView inAppWebView) { + @NonNull + private final String expectedBridgeSecret; + + public JavaScriptBridgeInterface(InAppWebView inAppWebView, @NonNull String expectedBridgeSecret) { this.inAppWebView = inAppWebView; + this.expectedBridgeSecret = expectedBridgeSecret; } @JavascriptInterface @@ -44,11 +50,60 @@ public void run() { } @JavascriptInterface - public void _callHandler(final String handlerName, final String _callHandlerID, final String args) { + public void _callHandler(final String jsonStringifiedData) { if (inAppWebView == null) { return; } + JSONObject data; + try { + data = new JSONObject(jsonStringifiedData); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, "Cannot convert jsonStringifiedData parameter of _callHandler method to a valid JSONObject"); + return; + } + + if (!data.has("handlerName") || data.isNull("handlerName")) { + Log.d(LOG_TAG, "handlerName is null or undefined"); + return; + } + + final String handlerName = data.optString("handlerName"); + final String bridgeSecret = data.optString("_bridgeSecret"); + final Integer _callHandlerID = data.optInt("_callHandlerID"); + final String origin = data.optString("origin"); + final String requestUrl = data.optString("requestUrl"); + final Boolean isMainFrame = data.optBoolean("isMainFrame"); + final String args = data.optString("args"); + + if (!expectedBridgeSecret.equals(bridgeSecret)) { + Log.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code from origin: " + origin); + return; + } + + boolean isOriginAllowed = false; + if (inAppWebView.customSettings.javaScriptHandlersOriginAllowList != null) { + for (Pattern allowedOrigin : inAppWebView.customSettings.javaScriptHandlersOriginAllowList) { + if (allowedOrigin.matcher(origin).matches()) { + isOriginAllowed = true; + break; + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true; + } + if (!isOriginAllowed) { + Log.e(LOG_TAG, "Bridge access attempt from an origin not allowed: " + origin); + return; + } + + if (inAppWebView.customSettings.javaScriptHandlersForMainFrameOnly && !isMainFrame) { + Log.e(LOG_TAG, "Bridge access attempt from a sub-frame origin: " + origin); + return; + } + // java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. // https://github.com/pichillilorenzo/flutter_inappwebview/issues/98 final Handler handler = new Handler(inAppWebView.getWebViewLooper()); @@ -60,84 +115,99 @@ public void run() { return; } - if (handlerName.equals("onPrintRequest") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - PrintJobSettings settings = new PrintJobSettings(); - settings.handledByClient = true; - final String printJobId = inAppWebView.printCurrentPage(settings); - if (inAppWebView != null && inAppWebView.channelDelegate != null) { - inAppWebView.channelDelegate.onPrintRequest(inAppWebView.getUrl(), printJobId, new WebViewChannelDelegate.PrintRequestCallback() { - @Override - public boolean nonNullSuccess(@NonNull Boolean handledByClient) { - return !handledByClient; - } + boolean isInternalHandler = true; + switch (handlerName) { + case "onPrintRequest": + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + PrintJobSettings settings = new PrintJobSettings(); + settings.handledByClient = true; + final String printJobId = inAppWebView.printCurrentPage(settings); + if (inAppWebView != null && inAppWebView.channelDelegate != null) { + inAppWebView.channelDelegate.onPrintRequest(inAppWebView.getUrl(), printJobId, new WebViewChannelDelegate.PrintRequestCallback() { + @Override + public boolean nonNullSuccess(@NonNull Boolean handledByClient) { + return !handledByClient; + } - @Override - public void defaultBehaviour(@Nullable Boolean handledByClient) { - if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) { - PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId); - if (printJobController != null) { - printJobController.disposeNoCancel(); + @Override + public void defaultBehaviour(@Nullable Boolean handledByClient) { + if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) { + PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId); + if (printJobController != null) { + printJobController.disposeNoCancel(); + } + } } - } - } - @Override - public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { - Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); - defaultBehaviour(null); + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }); } - }); - } - return; - } else if (handlerName.equals("callAsyncJavaScript")) { - try { - JSONArray arguments = new JSONArray(args); - JSONObject jsonObject = arguments.getJSONObject(0); - String resultUuid = jsonObject.getString("resultUuid"); - ValueCallback callAsyncJavaScriptCallback = inAppWebView.callAsyncJavaScriptCallbacks.get(resultUuid); - if (callAsyncJavaScriptCallback != null) { - callAsyncJavaScriptCallback.onReceiveValue(jsonObject.toString()); - inAppWebView.callAsyncJavaScriptCallbacks.remove(resultUuid); } - } catch (JSONException e) { - Log.e(LOG_TAG, "", e); - } - return; - } else if (handlerName.equals("evaluateJavaScriptWithContentWorld")) { - try { - JSONArray arguments = new JSONArray(args); - JSONObject jsonObject = arguments.getJSONObject(0); - String resultUuid = jsonObject.getString("resultUuid"); - ValueCallback evaluateJavaScriptCallback = inAppWebView.evaluateJavaScriptContentWorldCallbacks.get(resultUuid); - if (evaluateJavaScriptCallback != null) { - evaluateJavaScriptCallback.onReceiveValue(jsonObject.has("value") ? jsonObject.get("value").toString() : "null"); - inAppWebView.evaluateJavaScriptContentWorldCallbacks.remove(resultUuid); + break; + case "callAsyncJavaScript": + try { + JSONArray arguments = new JSONArray(args); + JSONObject jsonObject = arguments.getJSONObject(0); + String resultUuid = jsonObject.getString("resultUuid"); + ValueCallback callAsyncJavaScriptCallback = inAppWebView.callAsyncJavaScriptCallbacks.get(resultUuid); + if (callAsyncJavaScriptCallback != null) { + callAsyncJavaScriptCallback.onReceiveValue(jsonObject.toString()); + inAppWebView.callAsyncJavaScriptCallbacks.remove(resultUuid); + } + } catch (JSONException e) { + Log.e(LOG_TAG, "", e); } - } catch (JSONException e) { - Log.e(LOG_TAG, "", e); + break; + case "evaluateJavaScriptWithContentWorld": + try { + JSONArray arguments = new JSONArray(args); + JSONObject jsonObject = arguments.getJSONObject(0); + String resultUuid = jsonObject.getString("resultUuid"); + ValueCallback evaluateJavaScriptCallback = inAppWebView.evaluateJavaScriptContentWorldCallbacks.get(resultUuid); + if (evaluateJavaScriptCallback != null) { + evaluateJavaScriptCallback.onReceiveValue(jsonObject.has("value") ? jsonObject.get("value").toString() : "null"); + inAppWebView.evaluateJavaScriptContentWorldCallbacks.remove(resultUuid); + } + } catch (JSONException e) { + Log.e(LOG_TAG, "", e); + } + break; + default: + isInternalHandler = false; + break; + } + + if (isInternalHandler) { + if (inAppWebView != null) { + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].resolve(); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + + "}"; + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } return; } + if (inAppWebView.channelDelegate != null) { + JavaScriptHandlerFunctionData data = new JavaScriptHandlerFunctionData(origin, requestUrl, isMainFrame, args); // invoke flutter javascript handler and send back flutter data as a JSON Object to javascript - inAppWebView.channelDelegate.onCallJsHandler(handlerName, args, new WebViewChannelDelegate.CallJsHandlerCallback() { + inAppWebView.channelDelegate.onCallJsHandler(handlerName, data, new WebViewChannelDelegate.CallJsHandlerCallback() { @Override public void defaultBehaviour(@Nullable Object json) { if (inAppWebView == null) { // The webview has already been disposed, ignore. return; } - String sourceCode = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "] != null) { " + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "].resolve(" + json + "); " + - "delete window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "]; " + - "}"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); - } - else { - inAppWebView.loadUrl("javascript:" + sourceCode); - } + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].resolve(" + json + "); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + + "}"; + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } @Override @@ -150,16 +220,11 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj return; } - String sourceCode = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "] != null) { " + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "].reject(new Error(" + JSONObject.quote(message) + ")); " + - "delete window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "]; " + + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].reject(new Error(" + JSONObject.quote(message) + ")); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + "}"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); - } - else { - inAppWebView.loadUrl("javascript:" + sourceCode); - } + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } }); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java index 5f87b93c0..8bcf63ce6 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java @@ -29,6 +29,8 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.HitTestResult; import com.pichillilorenzo.flutter_inappwebview_android.types.HttpAuthResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.HttpAuthenticationChallenge; +import com.pichillilorenzo.flutter_inappwebview_android.types.InAppWebViewRect; +import com.pichillilorenzo.flutter_inappwebview_android.types.JavaScriptHandlerFunctionData; import com.pichillilorenzo.flutter_inappwebview_android.types.JsAlertResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsBeforeUnloadResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsConfirmResponse; @@ -39,6 +41,8 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.SafeBrowsingResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustAuthResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustChallenge; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.SslCertificateExt; import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; @@ -231,9 +235,9 @@ public void onReceiveValue(String value) { case getSettings: if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) { InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate(); - result.success(inAppBrowserActivity.getCustomSettings()); + result.success(inAppBrowserActivity.getCustomSettingsMap()); } else { - result.success((webView != null) ? webView.getCustomSettings() : null); + result.success((webView != null) ? webView.getCustomSettingsMap() : null); } break; case close: @@ -474,6 +478,23 @@ public void onReceiveValue(String value) { } result.success(true); break; + case requestFocus: + if (webView != null) { + boolean resultValue = false; + Integer direction = (Integer) call.argument("direction"); + InAppWebViewRect previouslyFocusedRect = InAppWebViewRect.fromMap((Map) call.argument("previouslyFocusedRect")); + if (direction != null && previouslyFocusedRect != null) { + resultValue = webView.requestFocus(direction, previouslyFocusedRect.toRect()); + } else if (direction != null) { + resultValue = webView.requestFocus(direction); + } else { + resultValue = webView.requestFocus(); + } + result.success(resultValue); + } else { + result.success(false); + } + break; case setContextMenu: if (webView != null) { Map contextMenu = (Map) call.argument("contextMenu"); @@ -675,6 +696,37 @@ public void onReceiveValue(Boolean value) { webView.clearFormData(); } result.success(true); + case hideInputMethod: + if (webView != null) { + webView.hideInputMethod(); + result.success(true); + } else { + result.success(false); + } + break; + case showInputMethod: + if (webView != null) { + webView.showInputMethod(); + result.success(true); + } else { + result.success(false); + } + break; + case saveState: + if (webView != null) { + result.success(webView.saveState()); + } else { + result.success(null); + } + break; + case restoreState: + if (webView != null) { + byte[] state = (byte[]) call.argument("state"); + result.success(webView.restoreState(state)); + } else { + result.success(false); + } + break; } } @@ -707,10 +759,10 @@ public void onScrollChanged(int x, int y) { channel.invokeMethod("onScrollChanged", obj); } - public void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) { + public void onDownloadStarting(DownloadStartRequest downloadStartRequest) { MethodChannel channel = getChannel(); if (channel == null) return; - channel.invokeMethod("onDownloadStartRequest", downloadStartRequest.toMap()); + channel.invokeMethod("onDownloadStarting", downloadStartRequest.toMap()); } public void onCreateContextMenu(HitTestResult hitTestResult) { @@ -1271,7 +1323,7 @@ public Object decodeResult(@Nullable Object obj) { } } - public void onCallJsHandler(String handlerName, String args, @NonNull CallJsHandlerCallback callback) { + public void onCallJsHandler(String handlerName, JavaScriptHandlerFunctionData data, @NonNull CallJsHandlerCallback callback) { MethodChannel channel = getChannel(); if (channel == null) { callback.defaultBehaviour(null); @@ -1279,7 +1331,7 @@ public void onCallJsHandler(String handlerName, String args, @NonNull CallJsHand } Map obj = new HashMap<>(); obj.put("handlerName", handlerName); - obj.put("args", args); + obj.put("data", data.toMap()); channel.invokeMethod("onCallJsHandler", obj, callback); } @@ -1310,6 +1362,23 @@ public void onRequestFocus() { channel.invokeMethod("onRequestFocus", obj); } + public static class ShowFileChooserCallback extends BaseCallbackResultImpl { + @Nullable + @Override + public ShowFileChooserResponse decodeResult(@Nullable Object obj) { + return ShowFileChooserResponse.fromMap((Map) obj); + } + } + + public void onShowFileChooser(ShowFileChooserRequest request, @NonNull ShowFileChooserCallback callback) { + MethodChannel channel = getChannel(); + if (channel == null) { + callback.defaultBehaviour(null); + return; + } + channel.invokeMethod("onShowFileChooser", request.toMap(), callback); + } + @Override public void dispose() { super.dispose(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java index 3c6d6ae1f..45f022ef6 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java @@ -62,6 +62,7 @@ public enum WebViewChannelDelegateMethods { saveWebArchive, zoomIn, zoomOut, + requestFocus, clearFocus, setContextMenu, requestFocusNodeHref, @@ -82,5 +83,9 @@ public enum WebViewChannelDelegateMethods { canScrollVertically, canScrollHorizontally, isInFullscreen, - clearFormData + clearFormData, + hideInputMethod, + showInputMethod, + saveState, + restoreState, } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java index bde634bb7..27a658979 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java @@ -18,10 +18,14 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.print.InAppWebViewPrintDocumentAdapter; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; +import android.print.PrintJob; import android.print.PrintManager; import android.text.TextUtils; import android.util.AttributeSet; @@ -71,7 +75,6 @@ import com.pichillilorenzo.flutter_inappwebview_android.content_blocker.ContentBlockerTrigger; import com.pichillilorenzo.flutter_inappwebview_android.find_interaction.FindInteractionController; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserDelegate; -import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.ConsoleLogJS; import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.InterceptAjaxRequestJS; import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.InterceptFetchRequestJS; import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; @@ -108,13 +111,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; -import java.util.regex.Pattern; import io.flutter.plugin.common.MethodChannel; final public class InAppWebView extends InputAwareWebView implements InAppWebViewInterface { - protected static final String LOG_TAG = "InAppWebView"; + private static final String LOG_TAG = "InAppWebView"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"; @Nullable @@ -141,7 +144,6 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie private boolean inFullscreen = false; public float zoomScale = 1.0f; public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); - public Pattern regexToCancelSubFramesLoadingCompiled; @Nullable public GestureDetector gestureDetector = null; @Nullable @@ -177,6 +179,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie @Nullable private PluginScript interceptOnlyAsyncAjaxRequestsPluginScript; + @NonNull + private final String expectedBridgeSecret = UUID.randomUUID().toString(); + private boolean javaScriptBridgeEnabled = true; + public InAppWebView(Context context) { super(context); } @@ -216,8 +222,8 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega } boolean isChromiumWebView = "com.android.webview".equals(packageInfo.packageName) || - "com.google.android.webview".equals(packageInfo.packageName) || - "com.android.chrome".equals(packageInfo.packageName); + "com.google.android.webview".equals(packageInfo.packageName) || + "com.android.chrome".equals(packageInfo.packageName); boolean isChromiumWebViewBugFixed = false; if (isChromiumWebView) { String versionName = packageInfo.versionName != null ? packageInfo.versionName : ""; @@ -225,7 +231,8 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega int majorVersion = versionName.contains(".") ? Integer.parseInt(versionName.split("\\.")[0]) : 0; isChromiumWebViewBugFixed = majorVersion >= 73; - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + } } if (isChromiumWebViewBugFixed || !isChromiumWebView) { @@ -237,14 +244,36 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega } } + @Override + public void setAlpha(float alpha) { + ViewParent parent = getParent(); + if (parent instanceof PullToRefreshLayout) { + ((PullToRefreshLayout) parent).setAlpha(alpha); + } else { + super.setAlpha(alpha); + } + } + @SuppressLint("RestrictedApi") public void prepare() { + if (customSettings.alpha != null) { + setAlpha(customSettings.alpha.floatValue()); + } + + javaScriptBridgeEnabled = customSettings.javaScriptBridgeEnabled; + if (customSettings.javaScriptBridgeOriginAllowList != null && customSettings.javaScriptBridgeOriginAllowList.isEmpty()) { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false; + } + if (plugin != null) { webViewAssetLoaderExt = WebViewAssetLoaderExt.fromMap(customSettings.webViewAssetLoader, plugin, getContext()); } - javaScriptBridgeInterface = new JavaScriptBridgeInterface(this); - addJavascriptInterface(javaScriptBridgeInterface, JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME); + if (javaScriptBridgeEnabled) { + javaScriptBridgeInterface = new JavaScriptBridgeInterface(this, expectedBridgeSecret); + addJavascriptInterface(javaScriptBridgeInterface, JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); + } inAppWebViewChromeClient = new InAppWebViewChromeClient(plugin, this, inAppBrowserDelegate); setWebChromeClient(inAppWebViewChromeClient); @@ -316,7 +345,8 @@ else if (customSettings.clearSessionCache) settings.setLoadWithOverviewMode(customSettings.loadWithOverviewMode); settings.setUseWideViewPort(customSettings.useWideViewPort); settings.setSupportZoom(customSettings.supportZoom); - settings.setTextZoom(customSettings.textZoom); + if (customSettings.textZoom != null) + settings.setTextZoom(customSettings.textZoom); setVerticalScrollBarEnabled(!customSettings.disableVerticalScroll && customSettings.verticalScrollBarEnabled); setHorizontalScrollBarEnabled(!customSettings.disableHorizontalScroll && customSettings.horizontalScrollBarEnabled); @@ -360,7 +390,13 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) settings.setForceDark(customSettings.forceDark); } if (customSettings.forceDarkStrategy != null && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { - WebSettingsCompat.setForceDarkStrategy(settings, customSettings.forceDarkStrategy); + try { + // for some reason the setForceDarkStrategy method could throw a ClassCastException + // from the Android WebView Chromium library. + WebSettingsCompat.setForceDarkStrategy(settings, customSettings.forceDarkStrategy); + } catch (Exception e) { + e.printStackTrace(); + } } settings.setGeolocationEnabled(customSettings.geolocationEnabled); if (customSettings.layoutAlgorithm != null) { @@ -389,12 +425,11 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) settings.setSaveFormData(customSettings.saveFormData); if (customSettings.incognito) setIncognito(true); - if (customSettings.hardwareAcceleration) - setLayerType(View.LAYER_TYPE_HARDWARE, null); - else - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - if (customSettings.regexToCancelSubFramesLoading != null) { - regexToCancelSubFramesLoadingCompiled = Pattern.compile(customSettings.regexToCancelSubFramesLoading); + if (customSettings.useHybridComposition) { + if (customSettings.hardwareAcceleration) + setLayerType(View.LAYER_TYPE_HARDWARE, null); + else + setLayerType(View.LAYER_TYPE_NONE, null); } setScrollBarStyle(customSettings.scrollBarStyle); if (customSettings.scrollBarDefaultDelayBeforeFade != null) { @@ -431,9 +466,6 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) (boolean) customSettings.rendererPriorityPolicy.get("waivedWhenNotVisible")); } - if (WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) { - WebSettingsCompat.setWillSuppressErrorPage(settings, customSettings.disableDefaultErrorPage); - } if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, customSettings.algorithmicDarkeningAllowed); } @@ -562,25 +594,44 @@ public boolean onLongClick(View v) { } public void prepareAndAddUserScripts() { - userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT); - interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(customSettings.interceptOnlyAsyncAjaxRequests); - if (customSettings.useShouldInterceptAjaxRequest) { - userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript); - userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useShouldInterceptFetchRequest) { - userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useOnLoadResource) { - userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT); - } - if (!customSettings.useHybridComposition) { - userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT); + if (javaScriptBridgeEnabled) { + // all the plugin scripts are using the JavaScript Bridge to work + userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + + final Set javaScriptBridgeOriginAllowList = customSettings.javaScriptBridgeOriginAllowList != null ? + customSettings.javaScriptBridgeOriginAllowList : customSettings.pluginScriptsOriginAllowList; + final boolean javaScriptBridgeForMainFrameOnly = customSettings.javaScriptBridgeForMainFrameOnly != null ? + customSettings.javaScriptBridgeForMainFrameOnly : customSettings.pluginScriptsForMainFrameOnly; + userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret, + javaScriptBridgeOriginAllowList, + javaScriptBridgeForMainFrameOnly)); + + userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList)); + userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList)); + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(customSettings.interceptOnlyAsyncAjaxRequests); + if (customSettings.useShouldInterceptAjaxRequest) { + userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript); + userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT( + customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly, + customSettings.useOnAjaxReadyStateChange, + customSettings.useOnAjaxProgress)); + } + if (customSettings.useShouldInterceptFetchRequest) { + userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } + if (customSettings.useOnLoadResource) { + userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } + if (!customSettings.useHybridComposition) { + userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } } this.userContentController.addUserOnlyScripts(this.initialUserOnlyScripts); } @@ -707,36 +758,21 @@ public void takeScreenshot(final @Nullable Map screenshotConfigu @Override public void run() { try { - Bitmap screenshotBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(screenshotBitmap); - c.translate(-getScrollX(), -getScrollY()); - draw(c); + int bitmapWidth = getMeasuredWidth(); + int bitmapHeight = getMeasuredHeight(); + int bitmapScrollX = getScrollX(); + int bitmapScrollY = getScrollY(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.PNG; int quality = 100; if (screenshotConfiguration != null) { Map rect = (Map) screenshotConfiguration.get("rect"); if (rect != null) { - int rectX = (int) Math.floor(rect.get("x") * pixelDensity + 0.5); - int rectY = (int) Math.floor(rect.get("y") * pixelDensity + 0.5); - int rectWidth = Math.min(screenshotBitmap.getWidth(), (int) Math.floor(rect.get("width") * pixelDensity + 0.5)); - int rectHeight = Math.min(screenshotBitmap.getHeight(), (int) Math.floor(rect.get("height") * pixelDensity + 0.5)); - screenshotBitmap = Bitmap.createBitmap( - screenshotBitmap, - rectX, - rectY, - rectWidth, - rectHeight); - } - - Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); - if (snapshotWidth != null) { - int dstWidth = (int) Math.floor(snapshotWidth * pixelDensity + 0.5); - float ratioBitmap = (float) screenshotBitmap.getWidth() / (float) screenshotBitmap.getHeight(); - int dstHeight = (int) ((float) dstWidth / ratioBitmap); - screenshotBitmap = Bitmap.createScaledBitmap(screenshotBitmap, dstWidth, dstHeight, true); + bitmapScrollX = (int) Math.floor(rect.get("x") * pixelDensity + 0.5); + bitmapScrollY = (int) Math.floor(rect.get("y") * pixelDensity + 0.5); + bitmapWidth = (int) Math.floor(rect.get("width") * pixelDensity + 0.5); + bitmapHeight = (int) Math.floor(rect.get("height") * pixelDensity + 0.5); } try { @@ -748,10 +784,31 @@ public void run() { quality = (Integer) screenshotConfiguration.get("quality"); } - screenshotBitmap.compress( + Bitmap screenshotBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(screenshotBitmap); + c.translate(-bitmapScrollX, -bitmapScrollY); + draw(c); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + if (screenshotConfiguration != null) { + Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); + if (snapshotWidth != null) { + int dstWidth = (int) Math.floor(snapshotWidth * pixelDensity + 0.5); + float ratioBitmap = (float) screenshotBitmap.getWidth() / (float) screenshotBitmap.getHeight(); + int dstHeight = (int) ((float) dstWidth / ratioBitmap); + screenshotBitmap = Bitmap.createScaledBitmap(screenshotBitmap, dstWidth, dstHeight, true); + } + } + + final boolean compressed = screenshotBitmap.compress( compressFormat, quality, byteArrayOutputStream); + if (!compressed) { + Log.e(LOG_TAG, "Screenshot cannot be compressed using compressFormat " + + compressFormat.name() + " with quality " + quality, null); + } try { byteArrayOutputStream.close(); @@ -779,15 +836,27 @@ public void setSettings(InAppWebViewSettings newCustomSettings, HashMap= Build.VERSION_CODES.N) @@ -958,7 +1029,13 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (newSettingsMap.get("forceDarkStrategy") != null && !customSettings.forceDarkStrategy.equals(newCustomSettings.forceDarkStrategy) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { - WebSettingsCompat.setForceDarkStrategy(settings, newCustomSettings.forceDarkStrategy); + try { + // for some reason the setForceDarkStrategy method could throw a ClassCastException + // from the Android WebView Chromium library. + WebSettingsCompat.setForceDarkStrategy(settings, newCustomSettings.forceDarkStrategy); + } catch (Exception e) { + e.printStackTrace(); + } } if (newSettingsMap.get("geolocationEnabled") != null && customSettings.geolocationEnabled != newCustomSettings.geolocationEnabled) @@ -1024,19 +1101,13 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) if (newSettingsMap.get("incognito") != null && customSettings.incognito != newCustomSettings.incognito) setIncognito(newCustomSettings.incognito); - if (newSettingsMap.get("hardwareAcceleration") != null && customSettings.hardwareAcceleration != newCustomSettings.hardwareAcceleration) { - if (newCustomSettings.hardwareAcceleration) - setLayerType(View.LAYER_TYPE_HARDWARE, null); - else - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - - if (newSettingsMap.get("regexToCancelSubFramesLoading") != null && (customSettings.regexToCancelSubFramesLoading == null || - !customSettings.regexToCancelSubFramesLoading.equals(newCustomSettings.regexToCancelSubFramesLoading))) { - if (newCustomSettings.regexToCancelSubFramesLoading == null) - regexToCancelSubFramesLoadingCompiled = null; - else - regexToCancelSubFramesLoadingCompiled = Pattern.compile(customSettings.regexToCancelSubFramesLoading); + if (customSettings.useHybridComposition) { + if (newSettingsMap.get("hardwareAcceleration") != null && customSettings.hardwareAcceleration != newCustomSettings.hardwareAcceleration) { + if (newCustomSettings.hardwareAcceleration) + setLayerType(View.LAYER_TYPE_HARDWARE, null); + else + setLayerType(View.LAYER_TYPE_NONE, null); + } } if (newCustomSettings.contentBlockers != null) { @@ -1101,11 +1172,6 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setHorizontalScrollbarTrackDrawable(new ColorDrawable(Color.parseColor(newCustomSettings.horizontalScrollbarTrackColor))); } - if (newSettingsMap.get("disableDefaultErrorPage") != null && - !Util.objEquals(customSettings.disableDefaultErrorPage, newCustomSettings.disableDefaultErrorPage) && - WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) { - WebSettingsCompat.setWillSuppressErrorPage(settings, newCustomSettings.disableDefaultErrorPage); - } if (newSettingsMap.get("algorithmicDarkeningAllowed") != null && !Util.objEquals(customSettings.algorithmicDarkeningAllowed, newCustomSettings.algorithmicDarkeningAllowed) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -1132,24 +1198,24 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) customSettings = newCustomSettings; } - public Map getCustomSettings() { + public Map getCustomSettingsMap() { return (customSettings != null) ? customSettings.getRealSettings(this) : null; } public void enablePluginScriptAtRuntime(final String flagVariable, final boolean enable, final PluginScript pluginScript) { - evaluateJavascript("window." + flagVariable, null, new ValueCallback() { + evaluateJavascript("((window.top == null || window.top === window) ? window : window.top)." + flagVariable, null, new ValueCallback() { @Override public void onReceiveValue(String value) { boolean alreadyLoaded = value != null && !value.equalsIgnoreCase("null"); if (alreadyLoaded) { - String enableSource = "window." + flagVariable + " = " + enable + ";"; + String enableSource = "((window.top == null || window.top === window) ? window : window.top)." + flagVariable + " = " + enable + ";"; evaluateJavascript(enableSource, null, null); if (!enable) { userContentController.removePluginScript(pluginScript); } - } else if (enable) { + } else if (enable && javaScriptBridgeEnabled) { evaluateJavascript(pluginScript.getSource(), null, null); userContentController.addPluginScript(pluginScript); } @@ -1169,8 +1235,8 @@ public void injectDeferredObject(String source, @Nullable final ContentWorld con } if (resultUuid != null && resultCallback != null) { evaluateJavaScriptContentWorldCallbacks.put(resultUuid, resultCallback); - scriptToInject = Util.replaceAll(PluginScriptsUtil.EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE, - PluginScriptsUtil.VAR_RANDOM_NAME, "_" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_" + Math.round(Math.random() * 1000000)) + scriptToInject = Util.replaceAll(PluginScriptsUtil.EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE(), + PluginScriptsUtil.VAR_RANDOM_NAME, "_" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_" + Math.round(Math.random() * 1000000)) .replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, UserContentController.escapeCode(source)) .replace(PluginScriptsUtil.VAR_RESULT_UUID, resultUuid); } @@ -1179,22 +1245,14 @@ public void injectDeferredObject(String source, @Nullable final ContentWorld con @Override public void run() { String scriptToInject = userContentController.generateCodeForScriptEvaluation(finalScriptToInject, contentWorld); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - // This action will have the side-effect of blurring the currently focused element - loadUrl("javascript:" + scriptToInject.replaceAll("[\r\n]+", "")); - if (contentWorld != null && resultCallback != null) { - resultCallback.onReceiveValue(""); + evaluateJavascript(scriptToInject, new ValueCallback() { + @Override + public void onReceiveValue(String s) { + if (resultUuid != null || resultCallback == null) + return; + resultCallback.onReceiveValue(s); } - } else { - evaluateJavascript(scriptToInject, new ValueCallback() { - @Override - public void onReceiveValue(String s) { - if (resultUuid != null || resultCallback == null) - return; - resultCallback.onReceiveValue(s); - } - }); - } + }); } }); } @@ -1215,15 +1273,15 @@ public void injectJavascriptFileFromUrl(String urlFile, @Nullable Map argumen String functionArgumentValues = TextUtils.join(", ", functionArgumentValuesList); String functionArgumentsObj = Util.JSONStringify(arguments); - String sourceToInject = PluginScriptsUtil.CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE + String sourceToInject = PluginScriptsUtil.CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE() .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, functionArgumentNames) .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, functionArgumentValues) .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, functionArgumentsObj) @@ -2032,6 +2108,69 @@ public void setChannelDelegate(@Nullable WebViewChannelDelegate channelDelegate) this.channelDelegate = channelDelegate; } + @Override + public InAppWebViewSettings getCustomSettings() { + return customSettings; + } + + @Override + public void showInputMethod() { + if (plugin == null || plugin.activity == null) { + return; + } + InputMethodManager imm = (InputMethodManager) plugin.activity.getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(this, 0); + } + } + + @Override + public void hideInputMethod() { + if (plugin == null || plugin.activity == null) { + return; + } + InputMethodManager imm = (InputMethodManager) plugin.activity.getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + IBinder windowToken = getWindowToken(); + if (!customSettings.useHybridComposition && containerView != null) { + windowToken = containerView.getWindowToken(); + } + imm.hideSoftInputFromWindow(windowToken, 0); + } + } + + @Override + @Nullable + public byte[] saveState() { + Bundle bundle = new Bundle(); + if (saveState(bundle) != null) { + Parcel parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } + return null; + } + + @Override + public boolean restoreState(byte[] state) { + boolean restored = false; + Parcel parcel = Parcel.obtain(); + try { + parcel.unmarshall(state, 0, state.length); + parcel.setDataPosition(0); + Bundle bundle = Bundle.CREATOR.createFromParcel(parcel); + restored = restoreState(bundle) != null; + } catch (Exception e) { + e.printStackTrace(); + } finally { + parcel.recycle(); + } + return restored; + } + + @Override public void dispose() { if (channelDelegate != null) { @@ -2041,7 +2180,7 @@ public void dispose() { super.dispose(); WebSettings settings = getSettings(); settings.setJavaScriptEnabled(false); - removeJavascriptInterface(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME); + removeJavascriptInterface(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE)) { WebViewCompat.setWebViewRenderProcessClient(this, null); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java index de544f972..120962808 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java @@ -1,5 +1,7 @@ package com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview; +import static android.app.Activity.RESULT_OK; + import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; @@ -43,16 +45,18 @@ import androidx.core.content.FileProvider; import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider; -import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; +import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.ActivityResultListener; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserDelegate; -import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; import com.pichillilorenzo.flutter_inappwebview_android.types.GeolocationPermissionShowPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsAlertResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsBeforeUnloadResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsConfirmResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.PermissionResponse; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview_android.webview.WebViewChannelDelegate; @@ -62,12 +66,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.PluginRegistry; -import static android.app.Activity.RESULT_OK; - public class InAppWebViewChromeClient extends WebChromeClient implements PluginRegistry.ActivityResultListener, ActivityResultListener { protected static final String LOG_TAG = "IABWebChromeClient"; @@ -76,7 +79,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR private static final int PICKER = 1; private static final int PICKER_LEGACY = 3; final String DEFAULT_MIME_TYPES = "*/*"; - final Map dialogs = new HashMap(); + final Map dialogs = new HashMap<>(); protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); @@ -126,9 +129,7 @@ public InAppWebViewChromeClient(@NonNull final InAppWebViewFlutterPlugin plugin, this.inAppBrowserDelegate.getActivityResultListeners().add(this); } - if (plugin.registrar != null) - plugin.registrar.addActivityResultListener(this); - else if (plugin.activityPluginBinding != null) + if (plugin.activityPluginBinding != null) plugin.activityPluginBinding.addActivityResultListener(this); } @@ -526,7 +527,7 @@ public void onCancel(DialogInterface dialog) { @Override public boolean onJsBeforeUnload(final WebView view, String url, final String message, - final JsResult result) { + final JsResult result) { if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onJsBeforeUnload(url, message, new WebViewChannelDelegate.JsBeforeUnloadCallback() { @Override @@ -636,13 +637,13 @@ public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGest String url = result.getExtra(); // Ensure that images with hyperlink return the correct URL, not the image source - if(result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { + if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { Message href = view.getHandler().obtainMessage(); view.requestFocusNodeHref(href); Bundle data = href.getData(); if (data != null) { String imageUrl = data.getString("url"); - if(imageUrl != null && !imageUrl.isEmpty()) { + if (imageUrl != null && !imageUrl.isEmpty()) { url = imageUrl; } } @@ -741,7 +742,7 @@ public boolean onConsoleMessage(ConsoleMessage consoleMessage) { consoleMessage.message(), consoleMessage.messageLevel().ordinal()); } - return true; + return super.onConsoleMessage(consoleMessage); } @Override @@ -802,8 +803,8 @@ public void onReceivedIcon(WebView view, Bitmap icon) { @Override public void onReceivedTouchIconUrl(WebView view, - String url, - boolean precomposed) { + String url, + boolean precomposed) { super.onReceivedTouchIconUrl(view, url, precomposed); InAppWebView webView = (InAppWebView) view; @@ -821,25 +822,78 @@ protected ViewGroup getRootView() { return (ViewGroup) activity.findViewById(android.R.id.content); } + private boolean onShowFileChooser(@NonNull ShowFileChooserRequest request, @NonNull ValueCallback filePathsCallback) { + WebViewChannelDelegate.ShowFileChooserCallback callback = new WebViewChannelDelegate.ShowFileChooserCallback() { + @Override + public boolean nonNullSuccess(@NonNull ShowFileChooserResponse response) { + if (response.isHandledByClient()) { + Uri[] uriArray = null; + if (response.getFilePaths() != null) { + uriArray = new Uri[response.getFilePaths().size()]; + for (int i = 0; i < response.getFilePaths().size(); i++) { + uriArray[i] = Uri.parse(response.getFilePaths().get(i)); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray); + } else { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray != null ? uriArray[0] : null); + } + return false; + } + return true; + } + + @Override + public void defaultBehaviour(@Nullable ShowFileChooserResponse response) { + String[] acceptTypes = request.getAcceptTypes().toArray(new String[0]); + boolean captureEnabled = request.isCaptureEnabled(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + boolean allowMultiple = request.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes, allowMultiple, captureEnabled); + } else { + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes.length > 0 ? acceptTypes[0] : "", captureEnabled); + } + } + + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }; + + if (inAppWebView != null && inAppWebView.channelDelegate != null && inAppWebView.customSettings.useOnShowFileChooser) { + inAppWebView.channelDelegate.onShowFileChooser(request, callback); + } else { + callback.defaultBehaviour(null); + } + + return true; + } + protected void openFileChooser(ValueCallback filePathCallback, String acceptType) { - startPickerIntent(filePathCallback, acceptType, null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback) { - startPickerIntent(filePathCallback, "", null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(""); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { - startPickerIntent(filePathCallback, acceptType, capture); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, true, null, null), filePathCallback); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - String[] acceptTypes = fileChooserParams.getAcceptTypes(); - boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; - boolean captureEnabled = fileChooserParams.isCaptureEnabled(); - return startPickerIntent(filePathCallback, acceptTypes, allowMultiple, captureEnabled); + return onShowFileChooser(ShowFileChooserRequest.fromFileChooserParams(fileChooserParams), filePathCallback); } @Override @@ -941,7 +995,7 @@ private Uri getCapturedMediaFile() { return null; } - public void startPickerIntent(ValueCallback filePathCallback, String acceptType, @Nullable String capture) { + public void startPickerIntent(ValueCallback filePathCallback, String acceptType, boolean captureEnabled) { filePathCallbackLegacy = filePathCallback; boolean images = acceptsImages(acceptType); @@ -949,12 +1003,11 @@ public void startPickerIntent(ValueCallback filePathCallback, String accept Intent pickerIntent = null; - if (capture != null) { + if (captureEnabled) { if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -997,8 +1050,7 @@ public boolean startPickerIntent(final ValueCallback callback, final Stri if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -1273,8 +1325,8 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequest(request.getOrigin().toString(), Arrays.asList(request.getResources()), null, callback); } else { @@ -1285,17 +1337,17 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj @Override public void onRequestFocus(WebView view) { - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onRequestFocus(); } } @Override public void onPermissionRequestCanceled(PermissionRequest request) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequestCanceled(request.getOrigin().toString(), - Arrays.asList(request.getResources())); + Arrays.asList(request.getResources())); } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java index c409efe54..aa5949526 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java @@ -71,8 +71,14 @@ public InAppWebViewClient(InAppBrowserDelegate inAppBrowserDelegate) { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResourceRequest request) { InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, request.getUrl().toString())) { + // Allow the request synchronously. + return false; + } + if (webView.customSettings.useShouldOverrideUrlLoading) { boolean isRedirect = false; if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT)) { @@ -88,39 +94,54 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request request.isForMainFrame(), request.hasGesture(), isRedirect); - if (webView.regexToCancelSubFramesLoadingCompiled != null) { - if (request.isForMainFrame()) - return true; - else { - Matcher m = webView.regexToCancelSubFramesLoadingCompiled.matcher(request.getUrl().toString()); - return m.matches(); - } - } else { - // There isn't any way to load an URL for a frame that is not the main frame, - // so if the request is not for the main frame, the navigation is allowed. - return request.isForMainFrame(); - } } + if (webView.customSettings.regexToCancelSubFramesLoading != null && !request.isForMainFrame()) { + Matcher m = webView.customSettings.regexToCancelSubFramesLoading.matcher(request.getUrl().toString()); + return m.matches(); + } + if (webView.customSettings.useShouldOverrideUrlLoading) { + // There isn't any way to load an URL for a frame that is not the main frame, + // so if the request is not for the main frame, the navigation is allowed. + return request.isForMainFrame(); + } + return false; } @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - InAppWebView inAppWebView = (InAppWebView) webView; - if (inAppWebView.customSettings.useShouldOverrideUrlLoading) { - onShouldOverrideUrlLoading(inAppWebView, url, "GET", null,true, false, false); + public boolean shouldOverrideUrlLoading(WebView view, String url) { + InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, url)) { + // Allow the request synchronously. + return false; + } + + if (webView.customSettings.useShouldOverrideUrlLoading) { + onShouldOverrideUrlLoading(webView, url, "GET", null,true, false, false); return true; } return false; } + private boolean allowSyncUrlLoading(InAppWebView webView, String url) { + if (webView.customSettings.regexToAllowSyncUrlLoading != null) { + Matcher m = webView.customSettings.regexToAllowSyncUrlLoading.matcher(url); + if (m.matches()) { + Log.d(LOG_TAG, "Request '" + url + "' automatically allowed as it is a match for 'regexToAllowSyncUrlLoading'."); + return true; + } + } + return false; + } + private void allowShouldOverrideUrlLoading(WebView webView, String url, @Nullable Map headers, boolean isForMainFrame) { if (isForMainFrame) { // There isn't any way to load an URL for a frame that is not the main frame, // so call this only on main frame. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && headers != null) webView.loadUrl(url, headers); else webView.loadUrl(url); @@ -164,7 +185,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.shouldOverrideUrlLoading(navigationAction, callback); } else { @@ -178,23 +199,16 @@ public void loadCustomJavaScriptOnPageStarted(WebView view) { if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { String source = webView.userContentController.generateWrappedCodeForDocumentStart(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); - } + webView.evaluateJavascript(source, (ValueCallback) null); } } public void loadCustomJavaScriptOnPageFinished(WebView view) { InAppWebView webView = (InAppWebView) view; - String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); } } @@ -216,7 +230,7 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { webView.channelDelegate.onLoadStart(url); } } - + public void onPageFinished(WebView view, String url) { final InAppWebView webView = (InAppWebView) view; webView.isLoading = false; @@ -237,13 +251,8 @@ public void onPageFinished(WebView view, String url) { CookieSyncManager.getInstance().sync(); } - String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(js, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); - } + String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE(); + webView.evaluateJavascript(js, (ValueCallback) null); if (webView.channelDelegate != null) { webView.channelDelegate.onLoadStop(url); @@ -260,13 +269,13 @@ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (inAppBrowserDelegate != null) { inAppBrowserDelegate.didUpdateVisitedHistory(url); } - + final InAppWebView webView = (InAppWebView) view; if (webView.channelDelegate != null) { webView.channelDelegate.onUpdateVisitedHistory(url, isReload); } } - + @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onReceivedError(WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceError error) { @@ -299,8 +308,7 @@ public void onReceivedError(WebView view, @NonNull WebResourceRequest request, @ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { final InAppWebView webView = (InAppWebView) view; - if (!WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE) && - webView.customSettings.disableDefaultErrorPage) { + if (webView.customSettings.disableDefaultErrorPage) { webView.stopLoading(); webView.loadUrl("about:blank"); } @@ -370,7 +378,7 @@ public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler credentialsProposed = CredentialDatabase.getInstance(view.getContext()).getHttpAuthCredentials(host, protocol, realm, port); URLCredential credentialProposed = null; - if (credentialsProposed != null && credentialsProposed.size() > 0) { + if (credentialsProposed != null && !credentialsProposed.isEmpty()) { credentialProposed = credentialsProposed.get(0); } @@ -397,7 +405,7 @@ public boolean nonNullSuccess(@NonNull HttpAuthResponse response) { handler.proceed(username, password); break; case 2: - if (credentialsProposed.size() > 0) { + if (!credentialsProposed.isEmpty()) { URLCredential credential = credentialsProposed.remove(0); handler.proceed(credential.getUsername(), credential.getPassword()); } else { @@ -430,7 +438,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedHttpAuthRequest(challenge, callback); } else { @@ -489,7 +497,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedServerTrustAuthRequest(challenge, callback); } else { @@ -529,7 +537,7 @@ public boolean nonNullSuccess(@NonNull ClientCertResponse response) { String certificatePath = (String) response.getCertificatePath(); String certificatePassword = (String) response.getCertificatePassword(); String keyStoreType = (String) response.getKeyStoreType(); - Util.PrivateKeyAndCertificates privateKeyAndCertificates = + Util.PrivateKeyAndCertificates privateKeyAndCertificates = Util.loadPrivateKeyAndCertificate(webView.plugin, certificatePath, certificatePassword, keyStoreType); if (privateKeyAndCertificates != null) { request.proceed(privateKeyAndCertificates.privateKey, privateKeyAndCertificates.certificates); @@ -708,7 +716,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque } WebResourceResponse response = null; - if (webView.contentBlockerHandler.getRuleList().size() > 0) { + if (!webView.contentBlockerHandler.getRuleList().isEmpty()) { try { response = webView.contentBlockerHandler.checkUrl(webView, request); } catch (Exception e) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java index 5aecaa5b4..1326d6915 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java @@ -73,6 +73,12 @@ public InAppWebViewClientCompat(InAppBrowserDelegate inAppBrowserDelegate) { @Override public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResourceRequest request) { InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, request.getUrl().toString())) { + // Allow the request synchronously. + return false; + } + if (webView.customSettings.useShouldOverrideUrlLoading) { boolean isRedirect = false; if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT)) { @@ -88,39 +94,54 @@ public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResou request.isForMainFrame(), request.hasGesture(), isRedirect); - if (webView.regexToCancelSubFramesLoadingCompiled != null) { - if (request.isForMainFrame()) - return true; - else { - Matcher m = webView.regexToCancelSubFramesLoadingCompiled.matcher(request.getUrl().toString()); - return m.matches(); - } - } else { - // There isn't any way to load an URL for a frame that is not the main frame, - // so if the request is not for the main frame, the navigation is allowed. - return request.isForMainFrame(); - } } + if (webView.customSettings.regexToCancelSubFramesLoading != null && !request.isForMainFrame()) { + Matcher m = webView.customSettings.regexToCancelSubFramesLoading.matcher(request.getUrl().toString()); + return m.matches(); + } + if (webView.customSettings.useShouldOverrideUrlLoading) { + // There isn't any way to load an URL for a frame that is not the main frame, + // so if the request is not for the main frame, the navigation is allowed. + return request.isForMainFrame(); + } + return false; } @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - InAppWebView inAppWebView = (InAppWebView) webView; - if (inAppWebView.customSettings.useShouldOverrideUrlLoading) { - onShouldOverrideUrlLoading(inAppWebView, url, "GET", null,true, false, false); + public boolean shouldOverrideUrlLoading(WebView view, String url) { + InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, url)) { + // Allow the request synchronously. + return false; + } + + if (webView.customSettings.useShouldOverrideUrlLoading) { + onShouldOverrideUrlLoading(webView, url, "GET", null,true, false, false); return true; } return false; } + private boolean allowSyncUrlLoading(InAppWebView webView, String url) { + if (webView.customSettings.regexToAllowSyncUrlLoading != null) { + Matcher m = webView.customSettings.regexToAllowSyncUrlLoading.matcher(url); + if (m.matches()) { + Log.d(LOG_TAG, "Request '" + url + "' automatically allowed as it is a match for 'regexToAllowSyncUrlLoading'."); + return true; + } + } + return false; + } + private void allowShouldOverrideUrlLoading(WebView webView, String url, @Nullable Map headers, boolean isForMainFrame) { if (isForMainFrame) { // There isn't any way to load an URL for a frame that is not the main frame, // so call this only on main frame. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && headers != null) webView.loadUrl(url, headers); else webView.loadUrl(url); @@ -164,7 +185,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.shouldOverrideUrlLoading(navigationAction, callback); } else { @@ -178,23 +199,16 @@ public void loadCustomJavaScriptOnPageStarted(WebView view) { if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { String source = webView.userContentController.generateWrappedCodeForDocumentStart(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); - } + webView.evaluateJavascript(source, (ValueCallback) null); } } public void loadCustomJavaScriptOnPageFinished(WebView view) { InAppWebView webView = (InAppWebView) view; - String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); } } @@ -216,7 +230,7 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { webView.channelDelegate.onLoadStart(url); } } - + public void onPageFinished(WebView view, String url) { final InAppWebView webView = (InAppWebView) view; webView.isLoading = false; @@ -237,13 +251,8 @@ public void onPageFinished(WebView view, String url) { CookieSyncManager.getInstance().sync(); } - String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(js, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); - } + String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE(); + webView.evaluateJavascript(js, (ValueCallback) null); if (webView.channelDelegate != null) { webView.channelDelegate.onLoadStop(url); @@ -260,13 +269,13 @@ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (inAppBrowserDelegate != null) { inAppBrowserDelegate.didUpdateVisitedHistory(url); } - + final InAppWebView webView = (InAppWebView) view; if (webView.channelDelegate != null) { webView.channelDelegate.onUpdateVisitedHistory(url, isReload); } } - + @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onReceivedError(@NonNull WebView view, @@ -309,8 +318,7 @@ public void onReceivedError(@NonNull WebView view, public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { final InAppWebView webView = (InAppWebView) view; - if (!WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE) && - webView.customSettings.disableDefaultErrorPage) { + if (webView.customSettings.disableDefaultErrorPage) { webView.stopLoading(); webView.loadUrl("about:blank"); } @@ -382,7 +390,7 @@ public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler credentialsProposed = CredentialDatabase.getInstance(view.getContext()).getHttpAuthCredentials(host, protocol, realm, port); URLCredential credentialProposed = null; - if (credentialsProposed != null && credentialsProposed.size() > 0) { + if (credentialsProposed != null && !credentialsProposed.isEmpty()) { credentialProposed = credentialsProposed.get(0); } @@ -409,7 +417,7 @@ public boolean nonNullSuccess(@NonNull HttpAuthResponse response) { handler.proceed(username, password); break; case 2: - if (credentialsProposed.size() > 0) { + if (!credentialsProposed.isEmpty()) { URLCredential credential = credentialsProposed.remove(0); handler.proceed(credential.getUsername(), credential.getPassword()); } else { @@ -442,7 +450,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedHttpAuthRequest(challenge, callback); } else { @@ -501,7 +509,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedServerTrustAuthRequest(challenge, callback); } else { @@ -541,7 +549,7 @@ public boolean nonNullSuccess(@NonNull ClientCertResponse response) { String certificatePath = (String) response.getCertificatePath(); String certificatePassword = (String) response.getCertificatePassword(); String keyStoreType = (String) response.getKeyStoreType(); - Util.PrivateKeyAndCertificates privateKeyAndCertificates = + Util.PrivateKeyAndCertificates privateKeyAndCertificates = Util.loadPrivateKeyAndCertificate(webView.plugin, certificatePath, certificatePassword, keyStoreType); if (privateKeyAndCertificates != null) { request.proceed(privateKeyAndCertificates.privateKey, privateKeyAndCertificates.certificates); @@ -735,7 +743,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque } WebResourceResponse response = null; - if (webView.contentBlockerHandler.getRuleList().size() > 0) { + if (!webView.contentBlockerHandler.getRuleList().isEmpty()) { try { response = webView.contentBlockerHandler.checkUrl(webView, request); } catch (Exception e) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java index 6bf17f607..9a157f981 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; public class InAppWebViewSettings implements ISettings { @@ -48,6 +49,8 @@ public class InAppWebViewSettings implements ISettings { public List>> contentBlockers = new ArrayList<>(); public Integer preferredContentMode = PreferredContentModeOptionType.RECOMMENDED.toValue(); public Boolean useShouldInterceptAjaxRequest = false; + public Boolean useOnAjaxReadyStateChange = false; + public Boolean useOnAjaxProgress = false; public Boolean interceptOnlyAsyncAjaxRequests = true; public Boolean useShouldInterceptFetchRequest = false; public Boolean incognito = false; @@ -60,7 +63,8 @@ public class InAppWebViewSettings implements ISettings { public Boolean allowFileAccessFromFileURLs = false; public Boolean allowUniversalAccessFromFileURLs = false; public Boolean allowBackgroundAudioPlaying = false; - public Integer textZoom = 100; + @Nullable + public Integer textZoom; /** * @deprecated */ @@ -72,9 +76,11 @@ public class InAppWebViewSettings implements ISettings { public Boolean domStorageEnabled = true; public Boolean useWideViewPort = true; public Boolean safeBrowsingEnabled = true; + @Nullable public Integer mixedContentMode; public Boolean allowContentAccess = true; public Boolean allowFileAccess = true; + @Nullable public String appCachePath; public Boolean blockNetworkImage = false; public Boolean blockNetworkLoads = false; @@ -86,8 +92,10 @@ public class InAppWebViewSettings implements ISettings { public Integer disabledActionModeMenuItems; public String fantasyFontFamily = "fantasy"; public String fixedFontFamily = "monospace"; - public Integer forceDark = 0; // WebSettingsCompat.FORCE_DARK_OFF - public Integer forceDarkStrategy = WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING; + @Nullable @Deprecated + public Integer forceDark; + @Nullable @Deprecated + public Integer forceDarkStrategy; public Boolean geolocationEnabled = true; public WebSettings.LayoutAlgorithm layoutAlgorithm; public Boolean loadWithOverviewMode = true; @@ -103,16 +111,22 @@ public class InAppWebViewSettings implements ISettings { public Boolean thirdPartyCookiesEnabled = true; public Boolean hardwareAcceleration = true; public Boolean supportMultipleWindows = false; - public String regexToCancelSubFramesLoading; + @Nullable + public Pattern regexToCancelSubFramesLoading; + @Nullable + public Pattern regexToAllowSyncUrlLoading; public Integer overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS; - public Boolean networkAvailable = null; + @Nullable + public Boolean networkAvailable; public Integer scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY; public Integer verticalScrollbarPosition = View.SCROLLBAR_POSITION_DEFAULT; - public Integer scrollBarDefaultDelayBeforeFade = null; + @Nullable + public Integer scrollBarDefaultDelayBeforeFade; public Boolean scrollbarFadingEnabled = true; - public Integer scrollBarFadeDuration = null; @Nullable - public Map rendererPriorityPolicy = null; + public Integer scrollBarFadeDuration; + @Nullable + public Map rendererPriorityPolicy; public Boolean useShouldInterceptRequest = false; public Boolean useOnRenderProcessGone = false; public Boolean disableDefaultErrorPage = false; @@ -133,6 +147,21 @@ public class InAppWebViewSettings implements ISettings { public byte[] defaultVideoPoster; @Nullable public Set requestedWithHeaderOriginAllowList; + @Nullable + public Set javaScriptHandlersOriginAllowList; + public Boolean javaScriptHandlersForMainFrameOnly = false; + public Boolean javaScriptBridgeEnabled = true; + @Nullable + public Set javaScriptBridgeOriginAllowList; + @Nullable + public Boolean javaScriptBridgeForMainFrameOnly; + @Nullable + public Set pluginScriptsOriginAllowList; + public Boolean pluginScriptsForMainFrameOnly = false; + public Boolean isUserInteractionEnabled = true; + @Nullable + public Double alpha; + public Boolean useOnShowFileChooser = false; @NonNull @Override @@ -193,6 +222,12 @@ public InAppWebViewSettings parse(@NonNull Map settings) { case "useShouldInterceptAjaxRequest": useShouldInterceptAjaxRequest = (Boolean) value; break; + case "useOnAjaxReadyStateChange": + useOnAjaxReadyStateChange = (Boolean) value; + break; + case "useOnAjaxProgress": + useOnAjaxProgress = (Boolean) value; + break; case "interceptOnlyAsyncAjaxRequests": interceptOnlyAsyncAjaxRequests = (Boolean) value; break; @@ -344,7 +379,10 @@ public InAppWebViewSettings parse(@NonNull Map settings) { supportMultipleWindows = (Boolean) value; break; case "regexToCancelSubFramesLoading": - regexToCancelSubFramesLoading = (String) value; + regexToCancelSubFramesLoading = Pattern.compile((String) value); + break; + case "regexToAllowSyncUrlLoading": + regexToAllowSyncUrlLoading = Pattern.compile((String) value); break; case "overScrollMode": overScrollMode = (Integer) value; @@ -412,6 +450,39 @@ public InAppWebViewSettings parse(@NonNull Map settings) { case "requestedWithHeaderOriginAllowList": requestedWithHeaderOriginAllowList = new HashSet<>((List) value); break; + case "javaScriptHandlersOriginAllowList": + javaScriptHandlersOriginAllowList = new HashSet<>(); + for (String pattern : (List) value) { + javaScriptHandlersOriginAllowList.add(Pattern.compile(pattern)); + } + break; + case "javaScriptHandlersForMainFrameOnly": + javaScriptHandlersForMainFrameOnly = (Boolean) value; + break; + case "javaScriptBridgeEnabled": + javaScriptBridgeEnabled = (Boolean) value; + break; + case "javaScriptBridgeOriginAllowList": + javaScriptBridgeOriginAllowList = new HashSet<>((List) value); + break; + case "javaScriptBridgeForMainFrameOnly": + javaScriptBridgeForMainFrameOnly = (Boolean) value; + break; + case "pluginScriptsOriginAllowList": + pluginScriptsOriginAllowList = new HashSet<>((List) value); + break; + case "pluginScriptsForMainFrameOnly": + pluginScriptsForMainFrameOnly = (Boolean) value; + break; + case "isUserInteractionEnabled": + isUserInteractionEnabled = (Boolean) value; + break; + case "alpha": + alpha = (Double) value; + break; + case "useOnShowFileChooser": + useOnShowFileChooser = (Boolean) value; + break; } } @@ -438,6 +509,8 @@ public Map toMap() { settings.put("contentBlockers", contentBlockers); settings.put("preferredContentMode", preferredContentMode); settings.put("useShouldInterceptAjaxRequest", useShouldInterceptAjaxRequest); + settings.put("useOnAjaxReadyStateChange", useOnAjaxReadyStateChange); + settings.put("useOnAjaxProgress", useOnAjaxProgress); settings.put("interceptOnlyAsyncAjaxRequests", interceptOnlyAsyncAjaxRequests); settings.put("useShouldInterceptFetchRequest", useShouldInterceptFetchRequest); settings.put("incognito", incognito); @@ -488,7 +561,8 @@ public Map toMap() { settings.put("thirdPartyCookiesEnabled", thirdPartyCookiesEnabled); settings.put("hardwareAcceleration", hardwareAcceleration); settings.put("supportMultipleWindows", supportMultipleWindows); - settings.put("regexToCancelSubFramesLoading", regexToCancelSubFramesLoading); + settings.put("regexToCancelSubFramesLoading", regexToCancelSubFramesLoading != null ? regexToCancelSubFramesLoading.pattern() : null); + settings.put("regexToAllowSyncUrlLoading", regexToAllowSyncUrlLoading != null ? regexToAllowSyncUrlLoading.pattern() : null); settings.put("overScrollMode", overScrollMode); settings.put("networkAvailable", networkAvailable); settings.put("scrollBarStyle", scrollBarStyle); @@ -511,6 +585,23 @@ public Map toMap() { settings.put("defaultVideoPoster", defaultVideoPoster); settings.put("requestedWithHeaderOriginAllowList", requestedWithHeaderOriginAllowList != null ? new ArrayList<>(requestedWithHeaderOriginAllowList) : null); + settings.put("javaScriptHandlersOriginAllowList", + javaScriptHandlersOriginAllowList != null ? new ArrayList() {{ + for (Pattern pattern : javaScriptHandlersOriginAllowList) { + add(pattern.pattern()); + } + }} : null); + settings.put("javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly); + settings.put("javaScriptBridgeEnabled", javaScriptBridgeEnabled); + settings.put("javaScriptBridgeOriginAllowList", + javaScriptBridgeOriginAllowList != null ? new ArrayList<>(javaScriptBridgeOriginAllowList) : null); + settings.put("javaScriptBridgeForMainFrameOnly", javaScriptBridgeForMainFrameOnly); + settings.put("pluginScriptsOriginAllowList", + pluginScriptsOriginAllowList != null ? new ArrayList<>(pluginScriptsOriginAllowList) : null); + settings.put("pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly); + settings.put("isUserInteractionEnabled", isUserInteractionEnabled); + settings.put("alpha", alpha); + settings.put("useOnShowFileChooser", useOnShowFileChooser); return settings; } @@ -521,6 +612,8 @@ public Map getRealSettings(@NonNull InAppWebViewInterface inAppW Map realSettings = toMap(); if (inAppWebView instanceof InAppWebView) { InAppWebView webView = (InAppWebView) inAppWebView; + realSettings.put("alpha", webView.getAlpha()); + WebSettings settings = webView.getSettings(); realSettings.put("userAgent", settings.getUserAgentString()); realSettings.put("javaScriptEnabled", settings.getJavaScriptEnabled()); @@ -557,7 +650,8 @@ public Map getRealSettings(@NonNull InAppWebViewInterface inAppW realSettings.put("defaultTextEncodingName", settings.getDefaultTextEncodingName()); if (WebViewFeature.isFeatureSupported(WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS)) { realSettings.put("disabledActionModeMenuItems", WebSettingsCompat.getDisabledActionModeMenuItems(settings)); - } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { realSettings.put("disabledActionModeMenuItems", settings.getDisabledActionModeMenuItems()); } realSettings.put("fantasyFontFamily", settings.getFantasyFontFamily()); @@ -596,9 +690,6 @@ public Map getRealSettings(@NonNull InAppWebViewInterface inAppW rendererPriorityPolicy.put("waivedWhenNotVisible", webView.getRendererPriorityWaivedWhenNotVisible()); realSettings.put("rendererPriorityPolicy", rendererPriorityPolicy); } - if (WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) { - realSettings.put("disableDefaultErrorPage", WebSettingsCompat.willSuppressErrorPage(settings)); - } if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { realSettings.put("algorithmicDarkeningAllowed", WebSettingsCompat.isAlgorithmicDarkeningAllowed(settings)); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java index b7d059c27..54230d09c 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java @@ -56,7 +56,7 @@ public void initJsInstance(InAppWebViewInterface webView, final ValueCallback() { @Override public void onReceiveValue(String value) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java index fe7326f70..164e3d3db 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java @@ -91,7 +91,7 @@ public void initJsInstance() { " var scheme = !isPageBlank ? window.location.protocol.replace(':', '') : null;" + " var host = !isPageBlank ? window.location.hostname : null;" + " var port = !isPageBlank ? window.location.port : null;" + - " if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._isOriginAllowed(allowedOriginRules, scheme, host, port)) {" + + " if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._isOriginAllowed(allowedOriginRules, scheme, host, port)) {" + " window['" + jsObjectNameEscaped + "'] = new FlutterInAppWebViewWebMessageListener('" + jsObjectNameEscaped + "');" + " }" + "})();"; @@ -101,7 +101,8 @@ public void initJsInstance() { UserScriptInjectionTime.AT_DOCUMENT_START, null, false, - null + webView.getCustomSettings().pluginScriptsOriginAllowList, + webView.getCustomSettings().pluginScriptsForMainFrameOnly )); } } diff --git a/flutter_inappwebview_android/example/pubspec.lock b/flutter_inappwebview_android/example/pubspec.lock index 42f7e4596..27dad23d8 100644 --- a/flutter_inappwebview_android/example/pubspec.lock +++ b/flutter_inappwebview_android/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -81,23 +81,22 @@ packages: path: ".." relative: true source: path - version: "1.0.11" + version: "1.2.0-beta.3" flutter_inappwebview_internal_annotations: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" - url: "https://pub.dev" - source: hosted - version: "1.0.10" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -121,6 +120,30 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -133,58 +156,58 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" platform: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" process: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -242,10 +265,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -258,26 +281,18 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 - url: "https://pub.dev" - source: hosted - version: "11.10.0" - web: - dependency: transitive - description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "14.2.5" webdriver: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_inappwebview_android/lib/src/cookie_manager.dart b/flutter_inappwebview_android/lib/src/cookie_manager.dart index c0e2fba5e..86ae5fe81 100755 --- a/flutter_inappwebview_android/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_android/lib/src/cookie_manager.dart @@ -209,6 +209,12 @@ class AndroidCookieManager extends PlatformCookieManager false; } + @override + Future flush() async { + Map args = {}; + await channel?.invokeMethod('flush', args); + } + @override void dispose() { // empty diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart index c53a36d57..3a057c971 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,8 +1,7 @@ -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 '../pull_to_refresh/pull_to_refresh_controller.dart'; import 'in_app_webview_controller.dart'; @@ -32,8 +31,10 @@ class AndroidHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -150,6 +151,7 @@ class AndroidHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -360,13 +362,24 @@ class AndroidHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = 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) { @@ -384,6 +397,10 @@ class AndroidHeadlessInAppWebView extends PlatformHeadlessInAppWebView settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart index e96a92b78..a585ae398 100755 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart @@ -1,16 +1,15 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; -import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// @@ -39,8 +38,10 @@ class AndroidInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -126,6 +127,7 @@ class AndroidInAppWebViewWidgetCreationParams super.onCameraCaptureStateChanged, super.onMicrophoneCaptureStateChanged, super.onContentSizeChanged, + super.onShowFileChooser, super.initialUrlRequest, super.initialFile, super.initialData, @@ -163,6 +165,7 @@ class AndroidInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -243,6 +246,7 @@ class AndroidInAppWebViewWidgetCreationParams onMicrophoneCaptureStateChanged: params.onMicrophoneCaptureStateChanged, onContentSizeChanged: params.onContentSizeChanged, + onShowFileChooser: params.onShowFileChooser, initialUrlRequest: params.initialUrlRequest, initialFile: params.initialFile, initialData: params.initialData, @@ -422,15 +426,24 @@ class AndroidInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + 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) { @@ -448,6 +461,10 @@ class AndroidInAppWebViewWidget extends PlatformInAppWebViewWidget { settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart index ceb4bc9a9..38c4ca88c 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,19 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; - import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [AndroidInAppWebViewController]. /// @@ -66,8 +46,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController 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 _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -363,11 +342,11 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -375,20 +354,25 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1406,19 +1390,42 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController .onContentSizeChanged(oldContentSize, newContentSize); } 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(); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1440,7 +1447,8 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1457,43 +1465,46 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! - .onAjaxProgress(request)) - ?.toNativeValue(); + return jsonEncode( + (await _inAppBrowserEventHandler!.onAjaxProgress(request)) + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1519,7 +1530,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1527,7 +1538,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1539,7 +1550,19 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + 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"'); @@ -1986,16 +2009,14 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2241,12 +2262,35 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController return InAppWebViewHitTestResult(type: type, extra: extra); } + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + @override Future clearFocus() async { Map args = {}; return await channel?.invokeMethod('clearFocus', args); } + @override + Future showInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('showInputMethod', args); + } + + @override + Future hideInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('hideInputMethod', args); + } + @override Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; @@ -2648,6 +2692,19 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('clearFormData', 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 getDefaultUserAgent() async { Map args = {}; @@ -2738,6 +2795,29 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController await _staticChannel.invokeMethod('clearAllCache', args); } + @override + Future enableSlowWholeDocumentDraw() async { + Map args = {}; + await _staticChannel.invokeMethod('enableSlowWholeDocumentDraw', 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) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_android/lib/src/inappwebview_platform.dart b/flutter_inappwebview_android/lib/src/inappwebview_platform.dart index 65e1b4268..aa5c42070 100644 --- a/flutter_inappwebview_android/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_android/lib/src/inappwebview_platform.dart @@ -155,7 +155,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [AndroidWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override AndroidWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -166,7 +166,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [AndroidLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override AndroidLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -177,7 +177,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override AndroidSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, diff --git a/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart index 768311549..cc92868d0 100644 --- a/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class AndroidPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [AndroidPrintJobControllerCreationParams] instance. const AndroidPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [AndroidPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory AndroidPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class AndroidPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return AndroidPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -43,6 +43,11 @@ class AndroidPrintJobController extends PlatformPrintJobController Future _handleMethod(MethodCall call) async { switch (call.method) { + case "onComplete": + bool completed = call.arguments["completed"]; + String? error = call.arguments["error"]; + onComplete?.call(completed, error); + break; default: throw UnimplementedError("Unimplemented ${call.method} method"); } diff --git a/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart index f8979f5cf..a6c0b9691 100644 --- a/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class AndroidWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_android/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_android/lib/src/web_storage/web_storage.dart index fdcefd6f8..ad2a3002d 100644 --- a/flutter_inappwebview_android/lib/src/web_storage/web_storage.dart +++ b/flutter_inappwebview_android/lib/src/web_storage/web_storage.dart @@ -70,7 +70,7 @@ class AndroidStorageCreationParams extends PlatformStorageCreationParams { } ///{@macro flutter_inappwebview_platform_interface.PlatformStorage} -abstract class AndroidStorage implements PlatformStorage { +abstract mixin class AndroidStorage implements PlatformStorage { @override AndroidInAppWebViewController? controller; diff --git a/flutter_inappwebview_android/lib/src/webview_asset_loader.dart b/flutter_inappwebview_android/lib/src/webview_asset_loader.dart index b54a7dcf4..8c3d0320f 100644 --- a/flutter_inappwebview_android/lib/src/webview_asset_loader.dart +++ b/flutter_inappwebview_android/lib/src/webview_asset_loader.dart @@ -25,7 +25,7 @@ class AndroidPathHandlerCreationParams } ///{@macro flutter_inappwebview_platform_interface.PlatformPathHandler} -abstract class AndroidPathHandler +abstract mixin class AndroidPathHandler implements ChannelController, PlatformPathHandler { final String _id = IdGenerator.generate(); @@ -54,7 +54,7 @@ abstract class AndroidPathHandler } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {"path": path, "type": type, "id": _id}; } @@ -194,7 +194,7 @@ class AndroidInternalStoragePathHandler String get directory => _internalParams.directory; @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {...toMap(), 'directory': directory}; } } diff --git a/flutter_inappwebview_android/pubspec.yaml b/flutter_inappwebview_android/pubspec.yaml index 9ba46c4be..801a592ce 100644 --- a/flutter_inappwebview_android/pubspec.yaml +++ b/flutter_inappwebview_android/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview_android description: Android implementation of the flutter_inappwebview plugin. -version: 1.0.12 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_android issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,19 +14,20 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.0.10 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 - plugin_platform_interface: ^2.0.2 + flutter_lints: ^4.0.0 + plugin_platform_interface: ^2.1.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter_inappwebview_ios/CHANGELOG.md b/flutter_inappwebview_ios/CHANGELOG.md index f7efb2c44..5bbf30252 100644 --- a/flutter_inappwebview_ios/CHANGELOG.md +++ b/flutter_inappwebview_ios/CHANGELOG.md @@ -1,3 +1,57 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Merged "Add proxy support for iOS" [#2362](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2362) (thanks to [yerkejs](https://github.com/yerkejs)) +- Fixed "[iOS] Webview opened with windowId does not receive javascript handler callback." [#2393](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2393) +- Fixed internal javascript callback handlers when the WebView has windowId not null +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `setInputMethodEnabled`, `hideInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "In iOS version 17.2, when moving the input focus in a WebView, an unknown area appears at the top of the screen." [#1947](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947) + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Implemented `requestFocus` WebView method +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Added support for `UserScript.allowedOriginRules` parameter +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- Fixed `show`, `hide` methods and `hidden` setting for `InAppBrowser` + +## 1.1.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.3.0 + +## 1.1.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.2.0 + +## 1.1.0+3 + +- Updated flutter_inappwebview_platform_interface version + +## ## 1.1.0+2 + +- Updated pubspec.yaml + +## 1.1.0+1 + +- Fixed "v6.1.0 fails to compile on Xcode 15" [#2288](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2288) + +## 1.1.0 + +- Fixed XCode 16 build +- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. +- Merged "Add privacy manifest for iOS" [#2029](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2029) (thanks to [ueman](https://github.com/ueman)) + ## 1.0.13 - Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10` diff --git a/flutter_inappwebview_ios/example/pubspec.lock b/flutter_inappwebview_ios/example/pubspec.lock index 36c07b45b..621957ae2 100644 --- a/flutter_inappwebview_ios/example/pubspec.lock +++ b/flutter_inappwebview_ios/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -79,25 +79,24 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_ios: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.0.13" + version: "1.2.0-beta.3" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" - url: "https://pub.dev" - source: hosted - version: "1.0.10" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -121,6 +120,30 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -133,58 +156,58 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" platform: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" process: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -242,10 +265,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -258,26 +281,18 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 - url: "https://pub.dev" - source: hosted - version: "11.10.0" - web: - dependency: transitive - description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "14.2.5" webdriver: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift b/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift index bc6602441..f76e4fbd2 100755 --- a/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift +++ b/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift @@ -14,7 +14,7 @@ public class CredentialDatabase: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift b/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift index cff9cc9e6..0c4a8723c 100644 --- a/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift +++ b/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift @@ -54,11 +54,9 @@ public class FindInteractionController: NSObject, Disposable { self.plugin = plugin self.webView = webView self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) } public func prepare() { @@ -99,7 +97,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else if find != "" { - let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');" + let startSearch = "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync('\(find)');" webView.evaluateJavaScript(startSearch, completionHandler: completionHandler) } } @@ -123,7 +121,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else { - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) } } @@ -143,7 +141,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else { - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches();", completionHandler: completionHandler) } } diff --git a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index 5d13086d7..dffc124da 100644 --- a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -20,7 +20,7 @@ public class HeadlessInAppWebView: Disposable { self.flutterWebView = flutterWebView self.plugin = plugin let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel) } diff --git a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 36d770c36..f0af5906d 100644 --- a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -19,7 +19,7 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift index 06219762e..0be2b5734 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -17,11 +17,11 @@ public class InAppBrowserManager: ChannelDelegate { static let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" static let NAV_STORYBOARD_CONTROLLER_ID = "navController" var plugin: SwiftFlutterPlugin? - - private var previousStatusBarStyle = -1 + + var navControllers: [String: InAppBrowserNavigationController?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -44,10 +44,6 @@ public class InAppBrowserManager: ChannelDelegate { } public func prepareInAppBrowserWebViewController(settings: [String: Any?]) -> InAppBrowserWebViewController { - if previousStatusBarStyle == -1 { - previousStatusBarStyle = UIApplication.shared.statusBarStyle.rawValue - } - let browserSettings = InAppBrowserSettings() let _ = browserSettings.parse(settings: settings) @@ -59,7 +55,6 @@ public class InAppBrowserManager: ChannelDelegate { webViewController.browserSettings = browserSettings webViewController.isHidden = browserSettings.hidden webViewController.webViewSettings = webViewSettings - webViewController.previousStatusBarStyle = previousStatusBarStyle return webViewController } @@ -105,17 +100,12 @@ public class InAppBrowserManager: ChannelDelegate { navController.pushViewController(webViewController, animated: false) webViewController.prepareNavigationControllerBeforeViewWillAppear() - var animated = true - if let browserSettings = webViewController.browserSettings, browserSettings.hidden { - animated = false - } - guard let visibleViewController = UIApplication.shared.visibleViewController else { assertionFailure("Failure init the visibleViewController!") return } - - if let popover = navController.popoverPresentationController { + + if let popover = webViewController.popoverPresentationController { let sourceView = visibleViewController.view ?? UIView() popover.sourceRect = CGRect(x: sourceView.bounds.midX, y: sourceView.bounds.midY, width: 0, height: 0) @@ -123,7 +113,13 @@ public class InAppBrowserManager: ChannelDelegate { popover.sourceView = sourceView } - visibleViewController.present(navController, animated: animated) + if let browserSettings = webViewController.browserSettings, browserSettings.hidden { + webViewController.loadViewIfNeeded() + } else { + visibleViewController.present(navController, animated: true) + } + + navControllers[webViewController.id] = navController } public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) { @@ -144,6 +140,11 @@ public class InAppBrowserManager: ChannelDelegate { public override func dispose() { super.dispose() + let navControllersValues = navControllers.values + navControllersValues.forEach { (navController: InAppBrowserNavigationController?) in + navController?.dismiss(animated: false) + } + navControllers.removeAll() plugin = nil } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift index 2d7b8c590..1ab8a3642 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift @@ -8,12 +8,8 @@ import Foundation public class InAppBrowserNavigationController: UINavigationController { - var tmpWindow: UIWindow? - deinit { debugPrint("InAppBrowserNavigationController - dealloc") - tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) - tmpWindow = nil UIApplication.shared.delegate?.window??.makeKeyAndVisible() } } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index c75434782..cee45cfa9 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -47,18 +47,17 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega var initialMimeType: String? var initialEncoding: String? var initialBaseUrl: String? - var previousStatusBarStyle = -1 var initialUserScripts: [[String: Any]] = [] var pullToRefreshInitialSettings: [String: Any?] = [:] var isHidden = false var menuItems: [InAppBrowserMenuItem] = [] public override func loadView() { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger()) + let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: plugin.registrar.messenger()) channelDelegate = InAppBrowserChannelDelegate(channel: channel) var userScripts: [UserScript] = [] @@ -214,7 +213,9 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public override func viewDidDisappear(_ animated: Bool) { - dispose() + if !isHidden { + dispose() + } super.viewDidDisappear(animated) } @@ -411,31 +412,27 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func show(completion: (() -> Void)? = nil) { - if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow { + if let visibleViewController = UIApplication.shared.visibleViewController, + let navigationController = navigationController { isHidden = false - window.alpha = 0.0 - window.isHidden = false - window.makeKeyAndVisible() - UIView.animate(withDuration: 0.2) { - window.alpha = 1.0 + visibleViewController.present(navigationController, animated: true) { completion?() } + } else { + completion?() } } public func hide(completion: (() -> Void)? = nil) { - if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow { + if let navigationController = navigationController { isHidden = true - window.alpha = 1.0 - UIView.animate(withDuration: 0.2) { - window.alpha = 0.0 - } completion: { (finished) in - if finished { - window.isHidden = true - UIApplication.shared.delegate?.window??.makeKeyAndVisible() - completion?() - } + navigationController.dismiss(animated: true) { + completion?() + UIApplication.shared.delegate?.window??.makeKeyAndVisible() } + } else { + completion?() + UIApplication.shared.delegate?.window??.makeKeyAndVisible() } } @@ -451,24 +448,31 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega public func close(completion: (() -> Void)? = nil) { if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! { - navigationController?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in + if let presentingViewController = navigationController?.presentingViewController { + presentingViewController.dismiss(animated: true, completion: {() -> Void in + completion?() + self.dispose() + }) + } else { completion?() - }) + dispose() + } } else { - navigationController?.parent?.dismiss(animated: true, completion: {() -> Void in + if let parent = navigationController?.parent { + parent.dismiss(animated: true, completion: {() -> Void in + completion?() + self.dispose() + }) + } else { completion?() - }) + dispose() + } } } @objc public func close() { - if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! { - navigationController?.presentingViewController?.dismiss(animated: true, completion: nil) - } - else { - navigationController?.parent?.dismiss(animated: true, completion: nil) - } + close(completion: nil) } @objc public func goBack() { @@ -633,9 +637,6 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega webView?.removeFromSuperview() webView = nil view = nil - if previousStatusBarStyle != -1, let statusBarStyle = UIStatusBarStyle(rawValue: previousStatusBarStyle) { - UIApplication.shared.statusBarStyle = statusBarStyle - } transitioningDelegate = nil searchBar?.delegate = nil closeButton?.target = nil @@ -644,6 +645,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega reloadButton?.target = nil shareButton?.target = nil menuButton?.target = nil + plugin?.inAppBrowserManager?.navControllers[id] = nil plugin = nil } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift index 9a0d06902..7a2f1f260 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -44,11 +44,9 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable webView = webViewTransport.webView webView!.id = viewId webView!.plugin = plugin - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger()) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) - } + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: plugin.registrar.messenger()) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) webView!.frame = myView!.bounds webView!.contextMenu = contextMenu webView!.initialUserScripts = userScripts diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift index 25dee18da..52c0bae70 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift @@ -7,7 +7,7 @@ import Flutter import Foundation -import WebKit +@preconcurrency import WebKit public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, @@ -69,6 +69,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, fileprivate var interceptOnlyAsyncAjaxRequestsPluginScript: PluginScript? + private var exceptedBridgeSecret = NSUUID().uuidString + private var javaScriptBridgeEnabled = true + init(id: Any?, plugin: SwiftFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, contextMenu: [String: Any]?, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) @@ -103,19 +106,44 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, set { super.frame = newValue - self.scrollView.contentInset = .zero + scrollView.contentInset = .zero if #available(iOS 11, *) { // Above iOS 11, adjust contentInset to compensate the adjustedContentInset so the sum will // always be 0. - if (scrollView.adjustedContentInset != UIEdgeInsets.zero) { - let insetToAdjust = self.scrollView.adjustedContentInset + if (scrollView.adjustedContentInset != .zero) { + let insetToAdjust = scrollView.adjustedContentInset scrollView.contentInset = UIEdgeInsets(top: -insetToAdjust.top, left: -insetToAdjust.left, - bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) + bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) } } } } + // Fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947 + private var _scrollViewContentInsetAdjusted = false + @objc func keyboardWillShow(notification: NSNotification) { + // UIResponder.keyboardWillShowNotification will be fired also + // when changing focus between HTML inputs with the keyboard already open + if (scrollView.adjustedContentInset != .zero) { + // if resizeToAvoidBottomInset is false on Flutter side, + // scrollView.adjustedContentInset.bottom will be > 0 + if scrollView.adjustedContentInset.bottom > 0 { + // if the scrollView.contentInset has already been fixed, do nothing + if !_scrollViewContentInsetAdjusted { + _scrollViewContentInsetAdjusted = true + let insetToAdjust = scrollView.adjustedContentInset + scrollView.contentInset = UIEdgeInsets(top: -insetToAdjust.top, left: -insetToAdjust.left, + bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) + } + } else { + scrollView.contentInset = .zero + } + } + } + @objc func keyboardWillHide(notification: NSNotification) { + _scrollViewContentInsetAdjusted = false + } + required public init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } @@ -189,7 +217,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, lastLongPressTouchPoint = touchLocation - evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in + evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in if error != nil { print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")") } else if let value = value as? [String: Any?] { @@ -345,6 +373,16 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } public func prepare() { + if #available(iOS 17.2, *) { + // Fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947 + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), + name: UIResponder.keyboardWillShowNotification, + object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + scrollView.addGestureRecognizer(self.longPressRecognizer) scrollView.addGestureRecognizer(self.recognizerForDisablingContextMenuOnLinks) scrollView.addGestureRecognizer(self.panGestureRecognizer) @@ -414,6 +452,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // } if let settings = settings { + isUserInteractionEnabled = settings.isUserInteractionEnabled + + if let viewAlpha = settings.alpha { + alpha = CGFloat(viewAlpha) + } + + javaScriptBridgeEnabled = settings.javaScriptBridgeEnabled + if let javaScriptBridgeOriginAllowList = settings.javaScriptBridgeOriginAllowList, javaScriptBridgeOriginAllowList.isEmpty { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false + } + if settings.transparentBackground { isOpaque = false backgroundColor = UIColor.clear @@ -546,56 +596,62 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // This is a limitation of the official WebKit API. return } - configuration.userContentController = WKUserContentController() configuration.userContentController.initialize() if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { return } - configuration.userContentController.addPluginScript(PROMISE_POLYFILL_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(CONSOLE_LOG_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(PRINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT) - if let settings = settings { - interceptOnlyAsyncAjaxRequestsPluginScript = createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests) - if settings.useShouldInterceptAjaxRequest { - if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + let pluginScriptsOriginAllowList = settings?.pluginScriptsOriginAllowList + let pluginScriptsForMainFrameOnly = settings?.pluginScriptsForMainFrameOnly ?? true + + let javaScriptBridgeOriginAllowList = settings?.javaScriptBridgeOriginAllowList ?? pluginScriptsOriginAllowList + let javaScriptBridgeForMainFrameOnly = settings?.javaScriptBridgeForMainFrameOnly ?? pluginScriptsForMainFrameOnly + + configuration.userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: exceptedBridgeSecret, allowedOriginRules: javaScriptBridgeOriginAllowList, forMainFrameOnly: javaScriptBridgeForMainFrameOnly)) + configuration.userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindElementsAtPointJS.FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(LastTouchedAnchorOrImageJS.LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindTextHighlightJS.FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OriginalViewPortMetaTagContentJS.ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + if let settings = settings { + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests, + allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly) + if settings.useShouldInterceptAjaxRequest { + if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { + configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + } + configuration.userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, + forMainFrameOnly: pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: settings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: settings.useOnAjaxProgress)) + } + if settings.useShouldInterceptFetchRequest { + configuration.userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if settings.useOnLoadResource { + configuration.userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if !settings.supportZoom { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + } else if settings.enableViewportScale { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) } - configuration.userContentController.addPluginScript(INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useShouldInterceptFetchRequest { - configuration.userContentController.addPluginScript(INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useOnLoadResource { - configuration.userContentController.addPluginScript(ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) - } - if !settings.supportZoom { - configuration.userContentController.addPluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - } else if settings.enableViewportScale { - configuration.userContentController.addPluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) } } - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") - configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived") configuration.userContentController.addUserOnlyScripts(initialUserScripts) configuration.userContentController.sync(scriptMessageHandler: self) } public static func preWKWebViewConfiguration(settings: InAppWebViewSettings?) -> WKWebViewConfiguration { let configuration = WKWebViewConfiguration() - + // initialzie WKUserContentController here to fix possible "undefined is not an object (evaluating 'window.webkit.messageHandlers')" javascript error + configuration.userContentController = WKUserContentController() configuration.processPool = WKProcessPoolManager.sharedProcessPool if let settings = settings { @@ -683,7 +739,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if let lastLongPressTouhLocation = lastLongPressTouchPoint { if configuration.preferences.javaScriptEnabled { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in if error != nil { print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")") } else if let value = value as? [String: Any?] { @@ -778,11 +834,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if #available(iOS 14.0, *) { let contentWorlds = configuration.userContentController.getContentWorlds(with: windowId) for contentWorld in contentWorlds { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source, contentWorld: contentWorld) } } else { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source) } } @@ -978,6 +1034,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } } + if newSettingsMap["isUserInteractionEnabled"] != nil && settings?.isUserInteractionEnabled != newSettings.isUserInteractionEnabled { + isUserInteractionEnabled = newSettings.isUserInteractionEnabled + } + + if newSettingsMap["alpha"] != nil, settings?.alpha != newSettings.alpha, let viewAlpha = newSettings.alpha { + alpha = CGFloat(viewAlpha) + } + if newSettingsMap["transparentBackground"] != nil && settings?.transparentBackground != newSettings.transparentBackground { if newSettings.transparentBackground { isOpaque = false @@ -1031,33 +1095,40 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if newSettingsMap["enableViewportScale"] != nil && settings?.enableViewportScale != newSettings.enableViewportScale { if !newSettings.enableViewportScale { - if configuration.userContentController.userScripts.contains(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) - evaluateJavaScript(NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(EnableViewportScaleJS.NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE()) } } else { - evaluateJavaScript(ENABLE_VIEWPORT_SCALE_JS_SOURCE) - configuration.userContentController.addUserScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) + evaluateJavaScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["supportZoom"] != nil && settings?.supportZoom != newSettings.supportZoom { if newSettings.supportZoom { - if configuration.userContentController.userScripts.contains(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - evaluateJavaScript(SUPPORT_ZOOM_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(SupportZoomJS.SUPPORT_ZOOM_JS_SOURCE()) } } else { - evaluateJavaScript(NOT_SUPPORT_ZOOM_JS_SOURCE) - configuration.userContentController.addUserScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) + evaluateJavaScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["useOnLoadResource"] != nil && settings?.useOnLoadResource != newSettings.useOnLoadResource { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE, - enable: newSettings.useOnLoadResource, - pluginScript: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: OnLoadResourceJS.FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE(), + enable: newSettings.useOnLoadResource, + pluginScript: OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useOnLoadResource = false } @@ -1065,28 +1136,58 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if newSettingsMap["useShouldInterceptAjaxRequest"] != nil && settings?.useShouldInterceptAjaxRequest != newSettings.useShouldInterceptAjaxRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptAjaxRequest, - pluginScript: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptAjaxRequest, + pluginScript: InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: newSettings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: newSettings.useOnAjaxProgress)) + } } else { newSettings.useShouldInterceptAjaxRequest = false } } + if newSettingsMap["useOnAjaxReadyStateChange"] != nil && settings?.useOnAjaxReadyStateChange != newSettings.useOnAjaxReadyStateChange { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(newSettings.useOnAjaxReadyStateChange);") + } + } else { + newSettings.useOnAjaxReadyStateChange = false + } + } + + if newSettingsMap["useOnAjaxProgress"] != nil && settings?.useOnAjaxProgress != newSettings.useOnAjaxProgress { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(newSettings.useOnAjaxProgress);") + } + } else { + newSettings.useOnAjaxProgress = false + } + } + if newSettingsMap["interceptOnlyAsyncAjaxRequests"] != nil && settings?.interceptOnlyAsyncAjaxRequests != newSettings.interceptOnlyAsyncAjaxRequests { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled, let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE, - enable: newSettings.interceptOnlyAsyncAjaxRequests, - pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE(), + enable: newSettings.interceptOnlyAsyncAjaxRequests, + pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + } } } if newSettingsMap["useShouldInterceptFetchRequest"] != nil && settings?.useShouldInterceptFetchRequest != newSettings.useShouldInterceptFetchRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptFetchRequest, - pluginScript: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptFetchRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptFetchRequest, + pluginScript: InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useShouldInterceptFetchRequest = false } @@ -1429,6 +1530,17 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } } +#if compiler(>=6.0) + public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? = nil) { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { + if let completionHandler = completionHandler { + completionHandler(nil, nil) + } + return + } + super.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) + } +#else public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) { if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { if let completionHandler = completionHandler { @@ -1438,6 +1550,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } super.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) } +#endif @available(iOS 14.0, *) public func evaluateJavaScript(_ javaScript: String, frame: WKFrameInfo? = nil, contentWorld: WKContentWorld, completionHandler: ((Result) -> Void)? = nil) { @@ -1519,7 +1632,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let functionArgumentNames = functionArgumentNamesList.joined(separator: ", ") let functionArgumentValues = functionArgumentValuesList.joined(separator: ", ") - jsToInject = CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS + jsToInject = CallAsyncJavaScriptBelowIOS14WrapperJS.CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, with: functionArgumentNames) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, with: functionArgumentValues) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, with: Util.JSONStringify(value: arguments)) @@ -1550,15 +1663,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, scriptAttributes += " script.id = '\(scriptIdEscaped)'; " scriptAttributes += """ script.onload = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); } }; """ scriptAttributes += """ script.onerror = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); } }; """ @@ -1764,7 +1877,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil // cancel the download @@ -1782,7 +1895,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: response.suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil } @@ -1875,7 +1988,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: navigationResponse.response.expectedContentLength, suggestedFilename: navigationResponse.response.suggestedFilename, textEncodingName: navigationResponse.response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) if useOnNavigationResponse == nil || !useOnNavigationResponse! { decisionHandler(.cancel) } @@ -1910,7 +2023,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, initializeWindowIdJS() InAppWebView.credentialsProposed = [] - evaluateJavaScript(PLATFORM_READY_JS_SOURCE, completionHandler: nil) + evaluateJavaScript(JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE, completionHandler: nil) // sometimes scrollView.contentSize doesn't fit all the frame.size available // so, we call setNeedsLayout to redraw the layout @@ -2060,7 +2173,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if let scheme = challenge.protectionSpace.protocol, scheme == "https" { // workaround for ProtectionSpace SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { if let sslCertificate = challenge.protectionSpace.sslCertificate { DispatchQueue.main.async { InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate @@ -2080,7 +2193,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, break case 1: // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let exceptions = SecTrustCopyExceptions(serverTrust) SecTrustSetExceptions(serverTrust, exceptions) let credential = URLCredential(trust: serverTrust) @@ -2782,167 +2895,250 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard javaScriptBridgeEnabled else { + return + } + guard let body = message.body as? [String: Any?] else { return } - if ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"].contains(message.name) { - var messageLevel = 1 - switch (message.name) { - case "consoleLog": - messageLevel = 1 - break - case "consoleDebug": - // on Android, console.debug is TIP - messageLevel = 0 - break - case "consoleError": - messageLevel = 3 - break - case "consoleInfo": - // on Android, console.info is LOG - messageLevel = 1 - break - case "consoleWarn": - messageLevel = 2 - break - default: - messageLevel = 1 - break + guard let bridgeSecret = body["_bridgeSecret"] as? String, bridgeSecret == exceptedBridgeSecret else { + print("Bridge access attempt with wrong secret token, possibly from malicious code from origin \(message.frameInfo.securityOrigin)") + return + } + + var sourceOrigin: URL? = nil + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + let requestUrl = message.frameInfo.request.url + + var isOriginAllowed = false + if let javaScriptHandlersOriginAllowList = settings?.javaScriptHandlersOriginAllowList { + if let origin = sourceOrigin?.absoluteString { + for allowedOrigin in javaScriptHandlersOriginAllowList { + if origin.range(of: allowedOrigin, options: .regularExpression, range: nil, locale: nil) != nil { + isOriginAllowed = true + break + } + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true + } + + if !isOriginAllowed { + print("Bridge access attempt from an origin not allowed: \(message.frameInfo.securityOrigin)") + return + } + + if message.name == "callHandler" { + guard let handlerName = body["handlerName"] as? String else { + print("handlerName is null or undefined") + return } - let consoleMessage = body["message"] as? String ?? "" let _windowId = body["_windowId"] as? Int64 var webView = self if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } - webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) - } else if message.name == "callHandler", let handlerName = body["handlerName"] as? String { - if handlerName == "onPrintRequest" { - let settings = PrintJobSettings() - settings.handledByClient = true - if let printJobId = printCurrentPage(settings: settings) { - let callback = WebViewChannelDelegate.PrintRequestCallback() - callback.nonNullSuccess = { (handledByClient: Bool) in - return !handledByClient + var isInternalHandler = true + switch (handlerName) { + case "onPrintRequest": + let settings = PrintJobSettings() + settings.handledByClient = true + if let printJobId = webView.printCurrentPage(settings: settings) { + let callback = WebViewChannelDelegate.PrintRequestCallback() + callback.nonNullSuccess = { (handledByClient: Bool) in + return !handledByClient + } + callback.defaultBehaviour = { (handledByClient: Bool?) in + if let printJob = webView.plugin?.printJobManager?.jobs[printJobId] { + printJob?.disposeNoDismiss() + } + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + webView.channelDelegate?.onPrintRequest(url: webView.url, printJobId: printJobId, callback: callback) } - callback.defaultBehaviour = { [weak self] (handledByClient: Bool?) in - if let printJob = self?.plugin?.printJobManager?.jobs[printJobId] { - printJob?.disposeNoDismiss() + break + case "onConsoleMessage": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first { + var messageLevel = 1 + switch (jsonData["level"] as? String) { + case "log": + messageLevel = 1 + break + case "debug": + // on Android, console.debug is TIP + messageLevel = 0 + break + case "error": + messageLevel = 3 + break + case "info": + // on Android, console.info is LOG + messageLevel = 1 + break + case "warn": + messageLevel = 2 + break + default: + messageLevel = 1 + break + } + let consoleMessage = jsonData["message"] as? String ?? "" + + webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) } } - callback.error = { [weak callback] (code: String, message: String?, details: Any?) in - print(code + ", " + (message ?? "")) - callback?.defaultBehaviour(nil) + break + case "onFindResultReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let findResult = jsonData["findResult"] as? [String: Any], + let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, + let numberOfMatches = findResult["numberOfMatches"] as? Int, + let isDoneCounting = findResult["isDoneCounting"] as? Bool { + webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + } } - channelDelegate?.onPrintRequest(url: url, printJobId: printJobId, callback: callback) - } - return + break + case "onCallAsyncJavaScriptResultBelowIOS14Received": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let resultUuid = jsonData["resultUuid"] as? String, + let result = webView.callAsyncJavaScriptBelowIOS14Results[resultUuid] { + result([ + "value": jsonData["value"], + "error": jsonData["error"] + ]) + webView.callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid) + } + } + break + case "onWebMessagePortMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let webMessageChannelId = jsonData["webMessageChannelId"] as? String, + let index = jsonData["index"] as? Int64 { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { + webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) + } + } + } + break + case "onWebMessageListenerPostMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, let jsObjectName = jsonData["jsObjectName"] as? String { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageListener = webView.webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { + let isMainFrame = message.frameInfo.isMainFrame + + var scheme: String? = nil + var host: String? = nil + var port: Int? = nil + if #available(iOS 9.0, *) { + let sourceOrigin = message.frameInfo.securityOrigin + scheme = sourceOrigin.protocol + host = sourceOrigin.host + port = sourceOrigin.port + } else if let url = message.frameInfo.request.url { + scheme = url.scheme + host = url.host + port = url.port + } + + if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { + return + } + + var sourceOrigin: URL? = nil + if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") + } + webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + } + } + } + break + default: + isInternalHandler = false + break } let _callHandlerID = body["_callHandlerID"] as? Int64 ?? 0 - let args = body["args"] as? String ?? "" - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView + if isInternalHandler { + evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; +} +""", completionHandler: nil) + return } + let args = body["args"] as? String ?? "" + let callback = WebViewChannelDelegate.CallJsHandlerCallback() - callback.defaultBehaviour = { [weak self] (response: Any?) in + callback.defaultBehaviour = { (response: Any?) in var json = "null" if let r = response as? String { json = r } - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].resolve(\(json)); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(\(json)); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } - callback.error = { [weak self] (code: String, message: String?, details: Any?) in + callback.error = { (code: String, message: String?, details: Any?) in let errorMessage = code + (message != nil ? ", " + (message ?? "") : "") print(errorMessage) - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } if let channelDelegate = webView.channelDelegate { - channelDelegate.onCallJsHandler(handlerName: handlerName, args: args, callback: callback) - } - } else if message.name == "onFindResultReceived", - let findResult = body["findResult"] as? [String: Any], - let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, - let numberOfMatches = findResult["numberOfMatches"] as? Int, - let isDoneCounting = findResult["isDoneCounting"] as? Bool { - - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - } else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received", - let resultUuid = body["resultUuid"] as? String, - let result = callAsyncJavaScriptBelowIOS14Results[resultUuid] { - result([ - "value": body["value"], - "error": body["error"] - ]) - callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid) - } else if message.name == "onWebMessagePortMessageReceived", - let webMessageChannelId = body["webMessageChannelId"] as? String, - let index = body["index"] as? Int64 { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageChannel = webMessageChannels[webMessageChannelId] { - webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) - } - } else if message.name == "onWebMessageListenerPostMessageReceived", let jsObjectName = body["jsObjectName"] as? String { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { - let isMainFrame = message.frameInfo.isMainFrame - - var scheme: String? = nil - var host: String? = nil - var port: Int? = nil - if #available(iOS 9.0, *) { - let sourceOrigin = message.frameInfo.securityOrigin - scheme = sourceOrigin.protocol - host = sourceOrigin.host - port = sourceOrigin.port - } else if let url = message.frameInfo.request.url { - scheme = url.scheme - host = url.host - port = url.port - } - - if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { - return - } - - var sourceOrigin: URL? = nil - if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { - sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") - } - webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + let data = JavaScriptHandlerFunctionData( + args: args, isMainFrame: message.frameInfo.isMainFrame, + origin: sourceOrigin?.absoluteString ?? "", + requestUrl: requestUrl?.absoluteString ?? "" + ) + channelDelegate.onCallJsHandler(handlerName: handlerName, data: data, callback: callback) } } } @@ -3082,7 +3278,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { public func getHitTestResult(completionHandler: @escaping (HitTestResult) -> Void) { if configuration.preferences.javaScriptEnabled, let lastTouchLocation = lastTouchPoint { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastTouchLocation.x),\(lastTouchLocation.y))", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(lastTouchLocation.x),\(lastTouchLocation.y))", completionHandler: {(value, error) in if error != nil { print("getHitTestResult error: \(error?.localizedDescription ?? "")") completionHandler(HitTestResult(type: .unknownType, extra: nil)) @@ -3102,7 +3298,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if configuration.preferences.javaScriptEnabled { // add some delay to make it sure _lastAnchorOrImageTouched is updated DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched", completionHandler: {(value, error) in let lastAnchorOrImageTouched = value as? [String: Any?] completionHandler(lastAnchorOrImageTouched, error) }) @@ -3116,7 +3312,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if configuration.preferences.javaScriptEnabled { // add some delay to make it sure _lastImageTouched is updated DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched", completionHandler: {(value, error) in let lastImageTouched = value as? [String: Any?] completionHandler(lastImageTouched, error) }) @@ -3126,8 +3322,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } - public func clearFocus() { - self.scrollView.subviews.first?.resignFirstResponder() + public func clearFocus() -> Bool { + return self.scrollView.subviews.first?.resignFirstResponder() ?? false + } + + public func requestFocus() -> Bool { + return self.scrollView.subviews.first?.becomeFirstResponder() ?? false } public func getCertificate() -> SslCertificate? { @@ -3208,7 +3408,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } @@ -3243,6 +3443,32 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { public override var inputAccessoryView: UIView? { return settings?.disableInputAccessoryView ?? false ? nil : super.inputAccessoryView } + + private var _inputMethodEnabled = true + public override var inputView: UIView? { + return _inputMethodEnabled ? super.inputView : UIView() + } + + public func setInputMethodEnabled(enabled: Bool) { + _inputMethodEnabled = enabled + for subview in self.scrollView.subviews { + subview.reloadInputViews() + } + } + + public func hideInputMethod() { + endEditing(true) + } + + @available(iOS 15.0, *) + public func saveState() -> Data? { + return interactionState is NSData || interactionState is Data ? interactionState as? Data : nil + } + + @available(iOS 15.0, *) + public func restoreState(state: Data) { + interactionState = state + } public func runWindowBeforeCreatedCallbacks() { let callbacks = windowBeforeCreatedCallbacks @@ -3280,9 +3506,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { interceptOnlyAsyncAjaxRequestsPluginScript = nil if windowId == nil { configuration.userContentController.removeAllPluginScriptMessageHandlers() - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") configuration.userContentController.removeAllUserScripts() if #available(iOS 11.0, *) { configuration.userContentController.removeAllContentRuleLists() diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift index df5c3b59e..e3397d178 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift @@ -19,7 +19,7 @@ public class InAppWebViewManager: ChannelDelegate { var windowAutoincrementId: Int64 = 0 init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -50,6 +50,14 @@ public class InAppWebViewManager: ChannelDelegate { clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: { result(true) }) + case "setJavaScriptBridgeName": + let bridgeName = arguments!["bridgeName"] as! String + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME(bridgeName: bridgeName) + result(true) + break + case "getJavaScriptBridgeName": + result(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) + break default: result(FlutterMethodNotImplemented) break diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift index 52ede006b..dc65eb0fc 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift @@ -27,6 +27,8 @@ public class InAppWebViewSettings: ISettings { var contentBlockers: [[String: [String : Any]]] = [] var minimumFontSize = 0 var useShouldInterceptAjaxRequest = false + var useOnAjaxReadyStateChange = false + var useOnAjaxProgress = false var interceptOnlyAsyncAjaxRequests = true var useShouldInterceptFetchRequest = false var incognito = false @@ -82,6 +84,14 @@ public class InAppWebViewSettings: ISettings { var maximumViewportInset: UIEdgeInsets? = nil var isInspectable = false var shouldPrintBackgrounds = false + var javaScriptHandlersOriginAllowList: [String]? = nil + var javaScriptBridgeEnabled = true + var javaScriptBridgeOriginAllowList: [String]? = nil + var javaScriptBridgeForMainFrameOnly = false + var pluginScriptsOriginAllowList: [String]? = nil + var pluginScriptsForMainFrameOnly = false + var isUserInteractionEnabled = true + var alpha: Double? = nil override init(){ super.init() @@ -97,6 +107,12 @@ public class InAppWebViewSettings: ISettings { maximumViewportInset = UIEdgeInsets.fromMap(map: maximumViewportInsetMap) settings.removeValue(forKey: "maximumViewportInset") } + // nullable values with primitive type (Int, Double, etc.) + // must be handled here as super.parse will not work + if let alphaValue = settings["alpha"] as? Double { + alpha = alphaValue + settings.removeValue(forKey: "alpha") + } let _ = super.parse(settings: settings) if #available(iOS 13.0, *) {} else { applePayAPIEnabled = false @@ -107,6 +123,8 @@ public class InAppWebViewSettings: ISettings { override func getRealSettings(obj: InAppWebView?) -> [String: Any?] { var realSettings: [String: Any?] = toMap() if let webView = obj { + realSettings["isUserInteractionEnabled"] = webView.isUserInteractionEnabled + realSettings["alpha"] = Double(webView.alpha) let configuration = webView.configuration if #available(iOS 9.0, *) { realSettings["userAgent"] = webView.customUserAgent diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index 8e7b3784d..354a62456 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -19,11 +19,9 @@ public class WebMessageChannel: FlutterMethodCallDelegate { self.id = id self.plugin = plugin super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.ports = [ WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self), WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self) @@ -35,7 +33,7 @@ public class WebMessageChannel: FlutterMethodCallDelegate { if let webView = self.webView { webView.evaluateJavascript(source: """ (function() { - \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"] = new MessageChannel(); })(); """) { (_) in completionHandler?(self) @@ -60,11 +58,11 @@ public class WebMessageChannel: FlutterMethodCallDelegate { ports.removeAll() webView?.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; if (webMessageChannel != null) { webMessageChannel.port1.close(); webMessageChannel.port2.close(); - delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + delete \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; } })(); """) diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift index e902aedb4..5b42e9bf2 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -23,11 +23,9 @@ public class WebMessageListener: FlutterMethodCallDelegate { self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) } public func assertOriginRulesValid() throws { @@ -96,23 +94,30 @@ public class WebMessageListener: FlutterMethodCallDelegate { }.joined(separator: ", ") let source = """ (function() { + \(WebMessageListener.isOriginAllowedJs) + var allowedOriginRules = [\(allowedOriginRulesString)]; var isPageBlank = window.location.href === "about:blank"; var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null; var host = !isPageBlank ? window.location.hostname : null; var port = !isPageBlank ? window.location.port : null; - if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) { + if (_isOriginAllowed(allowedOriginRules, scheme, host, port)) { window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)'); } })(); """ + + let allowedOriginRules = webView.settings?.pluginScriptsOriginAllowList + let forMainFrameOnly = webView.settings?.pluginScriptsForMainFrameOnly ?? true + webView.configuration.userContentController.addPluginScript(PluginScript( groupName: "WebMessageListener-" + id + "-" + jsObjectName, source: source, injectionTime: .atDocumentStart, - forMainFrameOnly: false, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, requiredInAllContentWorlds: false, - messageHandlerNames: ["onWebMessageListenerPostMessageReceived"] + messageHandlerNames: [] )) webView.configuration.userContentController.sync(scriptMessageHandler: webView) } @@ -177,6 +182,86 @@ public class WebMessageListener: FlutterMethodCallDelegate { } return false } + + private static let isOriginAllowedJs = """ + var _normalizeIPv6 = function(ip_string) { + // replace ipv4 address if any + var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); + if (ipv4) { + ip_string = ipv4[1]; + ipv4 = ipv4[2].match(/[0-9]+/g); + for (var i = 0;i < 4;i ++) { + var byte = parseInt(ipv4[i],10); + ipv4[i] = ("0" + byte.toString(16)).substr(-2); + } + ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; + } + + // take care of leading and trailing :: + ip_string = ip_string.replace(/^:|:$/g, ''); + + var ipv6 = ip_string.split(':'); + + for (var i = 0; i < ipv6.length; i ++) { + var hex = ipv6[i]; + if (hex != "") { + // normalize leading zeros + ipv6[i] = ("0000" + hex).substr(-4); + } + else { + // normalize grouped zeros :: + hex = []; + for (var j = ipv6.length; j <= 8; j ++) { + hex.push('0000'); + } + ipv6[i] = hex.join(':'); + } + } + + return ipv6.join(':'); + }; + + var _isOriginAllowed = function(allowedOriginRules, scheme, host, port) { + for (var rule of allowedOriginRules) { + if (rule === "*") { + return true; + } + if (scheme == null || scheme === "") { + continue; + } + if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { + continue; + } + var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; + var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; + var IPv6 = null; + if (rule.host != null && rule.host[0] === "[") { + try { + IPv6 = _normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); + } catch {} + } + var hostIPv6 = null; + try { + hostIPv6 = _normalizeIPv6(host); + } catch {} + + var schemeAllowed = scheme == rule.scheme; + + var hostAllowed = rule.host == null || + rule.host === "" || + host === rule.host || + (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || + (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); + + var portAllowed = rulePort === currentPort; + + if (schemeAllowed && hostAllowed && portAllowed) { + return true; + } + } + return false; + }; + """ public func dispose() { channelDelegate?.dispose() diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift index 46bab6d05..91aa7589a 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -345,8 +345,10 @@ public class WebViewChannelDelegate: ChannelDelegate { } break case .clearFocus: - webView?.clearFocus() - result(true) + result(webView?.clearFocus()) + break + case .requestFocus: + result(webView?.requestFocus()) break case .setContextMenu: if let webView = webView { @@ -675,6 +677,36 @@ public class WebViewChannelDelegate: ChannelDelegate { } else { result(false) } + break + case .setInputMethodEnabled: + if let webView = webView { + let enabled = arguments!["enabled"] as! Bool + webView.setInputMethodEnabled(enabled: enabled) + } + result(true) + break + case .hideInputMethod: + if let webView = webView { + webView.hideInputMethod() + } + result(true) + break + case .saveState: + if let webView = webView, #available(iOS 15.0, *) { + result(webView.saveState()) + } else { + result(nil) + } + break + case .restoreState: + if let webView = webView, #available(iOS 15.0, *) { + let state = arguments!["state"] as! FlutterStandardTypedData + webView.restoreState(state: state.data) + result(true) + } else { + result(false) + } + break } } @@ -705,8 +737,8 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onContentSizeChanged", arguments: arguments) } - public func onDownloadStartRequest(request: DownloadStartRequest) { - channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) + public func onDownloadStarting(request: DownloadStartRequest) { + channel?.invokeMethod("onDownloadStarting", arguments: request.toMap()) } public func onCreateContextMenu(hitTestResult: HitTestResult) { @@ -965,7 +997,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -997,7 +1029,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1029,7 +1061,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1083,14 +1115,14 @@ public class WebViewChannelDelegate: ChannelDelegate { } } - public func onCallJsHandler(handlerName: String, args: String, callback: CallJsHandlerCallback) { + public func onCallJsHandler(handlerName: String, data: JavaScriptHandlerFunctionData, callback: CallJsHandlerCallback) { if channel == nil { callback.defaultBehaviour(nil) return } let arguments: [String: Any?] = [ "handlerName": handlerName, - "args": args + "data": data.toMap() ] channel?.invokeMethod("onCallJsHandler", arguments: arguments, callback: callback) } @@ -1142,7 +1174,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift index 899cd769b..5f01e2273 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift @@ -59,6 +59,7 @@ public enum WebViewChannelDelegateMethods: String { case getSelectedText = "getSelectedText" case getHitTestResult = "getHitTestResult" case clearFocus = "clearFocus" + case requestFocus = "requestFocus" case setContextMenu = "setContextMenu" case requestFocusNodeHref = "requestFocusNodeHref" case requestImageRef = "requestImageRef" @@ -90,4 +91,8 @@ public enum WebViewChannelDelegateMethods: String { case getMicrophoneCaptureState = "getMicrophoneCaptureState" case setMicrophoneCaptureState = "setMicrophoneCaptureState" case loadSimulatedRequest = "loadSimulatedRequest" + case setInputMethodEnabled = "setInputMethodEnabled" + case hideInputMethod = "hideInputMethod" + case saveState = "saveState" + case restoreState = "restoreState" } diff --git a/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift b/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift index 235d8f9f5..49424d0ad 100755 --- a/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift @@ -16,7 +16,7 @@ public class MyCookieManager: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift b/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift index a375fdfb0..e86b442c4 100755 --- a/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift @@ -16,7 +16,7 @@ public class MyWebStorageManager: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift b/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift index a49d5316d..ae5902638 100644 --- a/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift +++ b/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift @@ -12,7 +12,7 @@ public class PlatformUtil: ChannelDelegate { var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -50,6 +50,7 @@ public class PlatformUtil: ChannelDelegate { static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String { let formatter = DateFormatter() + formatter.locale = locale formatter.dateFormat = format formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift index 5622691a1..f1f83db00 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift @@ -7,15 +7,28 @@ import Foundation -let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """ -(function(obj) { - (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { - \(PluginScriptsUtil.VAR_FUNCTION_BODY) - })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }).catch(function(error) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }); - return null; -})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); -""" +public class CallAsyncJavaScriptBelowIOS14WrapperJS { + + public static func CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() -> String { + return """ + (function(obj) { + (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { + \(PluginScriptsUtil.VAR_FUNCTION_BODY) + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': value, + 'error': null, + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }).catch(function(error) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': null, + 'error': error + '', + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }); + return null; + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift index d244d8993..5636ea62c 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift @@ -7,46 +7,59 @@ import Foundation -let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" - -let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript( - groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: CONSOLE_LOG_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"]) - -// the message needs to be concatenated with '' in order to have the same behavior like on Android -let CONSOLE_LOG_JS_SOURCE = """ -(function(console) { - - function _callHandler(oldLog, args) { - var message = ''; - for (var i in args) { - try { - message += message === '' ? args[i] : ' ' + args[i]; - } catch(ignored) {} - } - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId}); +public class ConsoleLogJS { + + public static let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame. + // Using it also on non-main frames could cause issues + // such as https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738 + public static func CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: CONSOLE_LOG_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) } - - var oldLogs = { - 'consoleLog': console.log, - 'consoleDebug': console.debug, - 'consoleError': console.error, - 'consoleInfo': console.info, - 'consoleWarn': console.warn - }; - - for (var k in oldLogs) { - (function(oldLog) { - console[oldLog.replace('console', '').toLowerCase()] = function() { - oldLogs[oldLog].apply(null, arguments); - _callHandler(oldLog, arguments); + + // the message needs to be concatenated with '' in order to have the same behavior like on Android + public static func CONSOLE_LOG_JS_SOURCE() -> String { + return """ + (function(console) { + + function _callHandler(logLevel, args) { + var message = ''; + for (var i in args) { + try { + message += message === '' ? args[i] : ' ' + args[i]; + } catch(_) {} + } + try { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).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); } - })(k); + })(window.console); + """ } -})(window.console); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift index 593ba00eb..efa07583a 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift @@ -7,30 +7,39 @@ import Foundation -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" - -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', 'width=device-width'); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" - -let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" +public class EnableViewportScaleJS { + + public static let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift index b3c22282e..26220f8a5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift @@ -7,67 +7,75 @@ import Foundation -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" - -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_ELEMENTS_AT_POINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -/** - https://developer.android.com/reference/android/webkit/WebView.HitTestResult - */ -let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) { - var 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 - }; - var element = document.elementFromPoint(x, y); - var data = { - type: 0, - extra: null - }; - while (element) { - if (element.tagName === 'IMG' && element.src) { - if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { - data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; - } else { - data.type = hitTestResultType.IMAGE_TYPE; - } - data.extra = element.src; - break; - } else if (element.tagName === 'A' && element.href) { - if (element.href.indexOf('mailto:') === 0) { - data.type = hitTestResultType.EMAIL_TYPE; - data.extra = element.href.replace('mailto:', ''); - } else if (element.href.indexOf('tel:') === 0) { - data.type = hitTestResultType.PHONE_TYPE; - data.extra = element.href.replace('tel:', ''); - } else if (element.href.indexOf('geo:') === 0) { - data.type = hitTestResultType.GEO_TYPE; - data.extra = element.href.replace('geo:', ''); - } else { - data.type = hitTestResultType.SRC_ANCHOR_TYPE; - data.extra = element.href; +public class FindElementsAtPointJS { + public static let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_ELEMENTS_AT_POINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + /** + https://developer.android.com/reference/android/webkit/WebView.HitTestResult + */ + public static func FIND_ELEMENTS_AT_POINT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint = function(x, y) { + var 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 + }; + var element = document.elementFromPoint(x, y); + var data = { + type: 0, + extra: null + }; + while (element) { + if (element.tagName === 'IMG' && element.src) { + if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { + data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; + } else { + data.type = hitTestResultType.IMAGE_TYPE; + } + data.extra = element.src; + break; + } else if (element.tagName === 'A' && element.href) { + if (element.href.indexOf('mailto:') === 0) { + data.type = hitTestResultType.EMAIL_TYPE; + data.extra = element.href.replace('mailto:', ''); + } else if (element.href.indexOf('tel:') === 0) { + data.type = hitTestResultType.PHONE_TYPE; + data.extra = element.href.replace('tel:', ''); + } else if (element.href.indexOf('geo:') === 0) { + data.type = hitTestResultType.GEO_TYPE; + data.extra = element.href.replace('geo:', ''); + } else { + data.type = hitTestResultType.SRC_ANCHOR_TYPE; + data.extra = element.href; + } + break; + } else if ( + (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || + element.tagName === 'TEXTAREA') { + data.type = hitTestResultType.EDIT_TEXT_TYPE + } + element = element.parentNode; } - break; - } else if ( - (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || - element.tagName === 'TEXTAREA') { - data.type = hitTestResultType.EDIT_TEXT_TYPE + return data; } - element = element.parentNode; + """ } - return data; } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift index f0db61452..5006f04b7 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift @@ -7,181 +7,180 @@ import Foundation -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" -let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount" -let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight" -let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting" - -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_TEXT_HIGHLIGHT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onFindResultReceived"]) - -let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """ -\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) { - if (element) { - if (element.nodeType == 3) { - // Text node - - var elementTmp = element; - while (true) { - var value = elementTmp.nodeValue; // Search for keyword in text node - var idx = value.toLowerCase().indexOf(keyword); - - if (idx < 0) break; - - var span = document.createElement("span"); - var text = document.createTextNode(value.substr(idx, keyword.length)); - span.appendChild(text); - - span.setAttribute( - "id", - "\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ); - span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00"; - span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); - - text = document.createTextNode(value.substr(idx + keyword.length)); - element.deleteData(idx, value.length - idx); - - var next = element.nextSibling; - element.parentNode.insertBefore(span, next); - element.parentNode.insertBefore(text, next); - element = text; - - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++; - elementTmp = document.createTextNode( - value.substr(idx + keyword.length) - ); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId +public class FindTextHighlightJS { + public static let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" + public static func FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._searchResultCount" + } + public static func FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._currentHighlight" + } + public static func FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._isDoneCounting" + } + + // This plugin is only for main frame + public static func FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_TEXT_HIGHLIGHT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func FIND_TEXT_HIGHLIGHT_JS_SOURCE() -> String { + return """ + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement = function(element, keyword) { + if (element) { + if (element.nodeType == 3) { + // Text node + + var elementTmp = element; + while (true) { + var value = elementTmp.nodeValue; // Search for keyword in text node + var idx = value.toLowerCase().indexOf(keyword); + + if (idx < 0) break; + + var span = document.createElement("span"); + var text = document.createTextNode(value.substr(idx, keyword.length)); + span.appendChild(text); + + span.setAttribute( + "id", + "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ); + span.setAttribute("class", "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) == 0 ? "#FF9732" : "#FFFF00"; + span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); + + text = document.createTextNode(value.substr(idx + keyword.length)); + element.deleteData(idx, value.length - idx); + + var next = element.nextSibling; + element.parentNode.insertBefore(span, next); + element.parentNode.insertBefore(text, next); + element = text; + + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE())++; + elementTmp = document.createTextNode( + value.substr(idx + keyword.length) + ); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + } else if (element.nodeType == 1) { + // Element node + if ( + element.style.display != "none" && + element.nodeName.toLowerCase() != "select" + ) { + for (var i = element.childNodes.length - 1; i >= 0; i--) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement( + element.childNodes[element.childNodes.length - 1 - i], + keyword + ); + } + } } - ); - } - } else if (element.nodeType == 1) { - // Element node - if ( - element.style.display != "none" && - element.nodeName.toLowerCase() != "select" - ) { - for (var i = element.childNodes.length - 1; i >= 0; i--) { - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement( - element.childNodes[element.childNodes.length - 1 - i], - keyword - ); + } } - } - } - } -} - -// the main entry point to start the search -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) { - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches(); - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase()); - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true; - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId - } - ); -} - -// helper function, recursively removes the highlights in elements and their children -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) { - if (element) { - if (element.nodeType == 1) { - if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") { - var text = element.removeChild(element.firstChild); - element.parentNode.insertBefore(text, element); - element.parentNode.removeChild(element); - return true; - } else { - var normalize = false; - for (var i = element.childNodes.length - 1; i >= 0; i--) { - if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) { - normalize = true; + + // the main entry point to start the search + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync = function(keyword) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches(); + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement(document.body, keyword.toLowerCase()); + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = true; + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + + // helper function, recursively removes the highlights in elements and their children + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement = function(element) { + if (element) { + if (element.nodeType == 1) { + if (element.getAttribute("class") == "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight") { + var text = element.removeChild(element.firstChild); + element.parentNode.insertBefore(text, element); + element.parentNode.removeChild(element); + return true; + } else { + var normalize = false; + for (var i = element.childNodes.length - 1; i >= 0; i--) { + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(element.childNodes[i])) { + normalize = true; + } + } + if (normalize) { + element.normalize(); + } + } + } } + return false; } - if (normalize) { - element.normalize(); + + // the main entry point to remove the highlights + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches = function() { + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(document.body); } - } - } - } - return false; -} - -// the main entry point to remove the highlights -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() { - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) { - if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return; - - var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1); - idx = - idx < 0 - ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1 - : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ? 0 - : idx; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx; - - var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx); - if (scrollTo) { - var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - for (var i = 0; i < highlights.length; i++) { - var span = highlights[i]; - span.style.backgroundColor = "#FFFF00"; - } - scrollTo.style.backgroundColor = "#FF9732"; - - scrollTo.scrollIntoView({ - behavior: "auto", - block: "center" - }); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext = function(forward) { + if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) <= 0) return; + + var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) + (forward ? +1 : -1); + idx = + idx < 0 + ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) - 1 + : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ? 0 + : idx; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = idx; + + var scrollTo = document.getElementById("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + idx); + if (scrollTo) { + var highlights = document.getElementsByClassName("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + for (var i = 0; i < highlights.length; i++) { + var span = highlights[i]; + span.style.backgroundColor = "#FFFF00"; + } + scrollTo.style.backgroundColor = "#FF9732"; + + scrollTo.scrollIntoView({ + behavior: "auto", + block: "center" + }); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } } - ); - } + """ + } } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift index 28ebb58b3..e8ca40c76 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift @@ -7,261 +7,288 @@ import Foundation -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest" - -let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests"; - -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_AJAX_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript { - return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);", - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: [] - ); -} - -let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true; -(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_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(new Uint8Array(request.response)); - return; - case 'blob': - const reader = new FileReader(); - reader.addEventListener('loadend', function() { - callback(new Uint8Array(reader.result)); - }); - reader.readAsArrayBuffer(blob); - return; - case 'document': - callback(request.response.documentElement.outerHTML); - return; - case 'json': - callback(request.response); - return; - }; +public class InterceptAjaxRequestJS { + + public static let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptAjaxRequest" } - 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) { - var self = this; - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - }); - }); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxReadyStateChange" } - }; - ajax.prototype.send = function(data) { - var self = this; - var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false; - if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) { - if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) { - this._flutter_inappwebview_already_onreadystatechange_wrapped = true; - var onreadystatechange = this.onreadystatechange; - this.onreadystatechange = function() { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - if (onreadystatechange != null) { - onreadystatechange(); - } - }); - }); - } else if (onreadystatechange != null) { - onreadystatechange(); - } - }; - } - 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); - \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) { - var ajaxRequest = { - data: data, - 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxProgress" + } + + public static func FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._interceptOnlyAsyncAjaxRequests" + } + + public static func INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool, initialUseOnAjaxReadyStateChange: Bool = false, initialUseOnAjaxProgress: Bool = false) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress: initialUseOnAjaxProgress), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) = \(onlyAsync);", + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: [] + ); + } + + public static func INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: Bool, initialUseOnAjaxProgress: Bool) -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) = true; + \(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(initialUseOnAjaxReadyStateChange); + \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(initialUseOnAjaxProgress); + (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_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(new Uint8Array(request.response)); + return; + case 'blob': + const reader = new FileReader(); + reader.addEventListener('loadend', function() { + callback(new Uint8Array(reader.result)); + }); + reader.readAsArrayBuffer(blob); + return; + case 'document': + callback(request.response.documentElement.outerHTML); + return; + case 'json': + callback(request.response); return; }; - if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).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 (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) { - data = result.data; - } else if (result.data.length > 0) { - data = new Uint8Array(result.data); + } + 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 (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) === false || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) == null || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) === false) { + return; + } + var self = this; + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == 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; + }); } - 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; + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + }); + }); + } + }; + ajax.prototype.send = function(data) { + var self = this; + var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) === false; + if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true)) { + if (\(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) { + this._flutter_inappwebview_already_onreadystatechange_wrapped = true; + var realOnreadystatechange = this.onreadystatechange; + this.onreadystatechange = function() { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }); + }); + } else if (realOnreadystatechange != null) { + realOnreadystatechange(); } - 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); - } + 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); + \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(data).then(function(data) { + var ajaxRequest = { + data: data, + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + if (result.data != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) && result.data.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.data); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) || result.data == null) { + data = result.data; + } else if (result.data.length > 0) { + data = 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, data); + }); + }); + } else { + send.call(this, data); } - send.call(self, data); - }); - }); - } else { - send.call(this, data); + }; + })(window.XMLHttpRequest); + """ } - }; -})(window.XMLHttpRequest); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift index 14539811c..953fa3aa8 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift @@ -7,147 +7,157 @@ import Foundation -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest" - -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_FETCH_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true; -(function(fetch) { - if (fetch == null) { - return; - } - window.fetch = async function(resource, init) { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == 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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers); - } - fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials); - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) { - fetchRequest.body = body; - return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { - if (result != null) { - switch (result.action) { - case 0: - var controller = new AbortController(); +public class InterceptFetchRequestJS { + + public static let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptFetchRequest" + } + + public static func INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) = true; + (function(fetch) { + if (fetch == null) { + return; + } + window.fetch = async function(resource, init) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == 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) { - init.signal = controller.signal; - } else { - init = { - signal: controller.signal - }; - } - controller.abort(); - break; - } - if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).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 }; + 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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertHeadersToJson(fetchRequest.headers); + } + fetchRequest.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertCredentialsToJson(fetchRequest.credentials); + return \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(fetchRequest.body).then(function(body) { + fetchRequest.body = body; + return window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { + if (result != null) { + switch (result.action) { + case 0: + var controller = new AbortController(); + if (init != null) { + init.signal = controller.signal; + } else { + init = { + signal: controller.signal + }; + } + controller.abort(); + break; + } + if (result.body != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) && result.body.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.body); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToHeaders(result.headers); + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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); } - 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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).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 = \(JAVASCRIPT_UTIL_VAR_NAME).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); + """ } - }; -})(window.fetch); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift index 1549c28be..0e42a3734 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift @@ -7,237 +7,287 @@ import Foundation -let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" - -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: JAVASCRIPT_BRIDGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["callHandler"]) - -let JAVASCRIPT_BRIDGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME) = {}; -\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {}; -window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - var _callHandlerID = setTimeout(function(){}); - window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} ); - return new Promise(function(resolve, reject) { - window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject}; - }); -}; -\(WEB_MESSAGE_LISTENER_JS_SOURCE) -\(UTIL_JS_SOURCE) -""" - -let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; +public class JavaScriptBridgeJS { + private static var _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" + public static func set_JAVASCRIPT_BRIDGE_NAME(bridgeName: String) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName + } + public static func get_JAVASCRIPT_BRIDGE_NAME() -> String { + return _JAVASCRIPT_BRIDGE_NAME + } + + public static let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" + + public static let VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET" -let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util" + public static func JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: String, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + let source = JAVASCRIPT_BRIDGE_JS_SOURCE().replacingOccurrences(of: VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, with: expectedBridgeSecret) + return PluginScript( + groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: source, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: ["callHandler"]) + } -/* - https://github.com/github/fetch/blob/master/fetch.js - */ -let UTIL_JS_SOURCE = """ -\(JAVASCRIPT_UTIL_VAR_NAME) = { - support: { - searchParams: 'URLSearchParams' in window, - iterable: 'Symbol' in window && 'iterator' in Symbol, - blob: - 'FileReader' in window && - 'Blob' in window && - (function() { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - })(), - formData: 'FormData' in window, - arrayBuffer: 'ArrayBuffer' in window - }, - isDataView: function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj); - }, - fileReaderReady: function(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result); - }; - reader.onerror = function() { - reject(reader.error); - }; - }); - }, - readBlobAsArrayBuffer: function(blob) { - var reader = new FileReader(); - var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader); - reader.readAsArrayBuffer(blob); - return promise; - }, - convertBodyToArrayBuffer: function(body) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ]; - var isArrayBufferView = null; - if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) { - isArrayBufferView = - ArrayBuffer.isView || - function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + public static func JAVASCRIPT_BRIDGE_JS_SOURCE() -> String { + return """ + window.\(get_JAVASCRIPT_BRIDGE_NAME()) = {}; + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME()) = {}; + (function(window) { + var bridgeSecret = '\(VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET)'; + var _JSON_stringify; + var _Array_slice; + var _setTimeout; + var _Promise; + var _UserMessageHandler; + var _postMessage; + try { + _JSON_stringify = window.JSON.stringify; + _Array_slice = window.Array.prototype.slice; + _Array_slice.call = window.Function.prototype.call; + _setTimeout = window.setTimeout; + _Promise = window.Promise; + _UserMessageHandler = window.webkit.messageHandlers['callHandler']; + _postMessage = _UserMessageHandler.postMessage; + _postMessage.call = window.Function.prototype.call; + } catch (_) { return; } + window.\(get_JAVASCRIPT_BRIDGE_NAME()).callHandler = function() { + var _windowId = \(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()); + var _callHandlerID = _setTimeout(function(){}); + _postMessage.call(_UserMessageHandler, { + 'handlerName': arguments[0], + '_callHandlerID': _callHandlerID, + '_bridgeSecret': bridgeSecret, + 'args': _JSON_stringify(_Array_slice.call(arguments, 1)), + '_windowId': _windowId + }); + return new _Promise(function(resolve, reject) { + try { + (window.top === window ? window : window.top).\(get_JAVASCRIPT_BRIDGE_NAME())[_callHandlerID] = {resolve: resolve, reject: reject}; + } catch (e) { + resolve(); + } + }); }; - } + })(window); + \(WebMessageListenerJS.WEB_MESSAGE_LISTENER_JS_SOURCE()) + \(UTIL_JS_SOURCE()) + """ + } - var bodyUsed = false; + public static let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; - this._bodyInit = body; - if (!body) { - this._bodyText = ''; - } else if (typeof body === 'string') { - this._bodyText = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString(); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer); - this._bodyInit = new Blob([this._bodyArrayBuffer]); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body); - } else { - this._bodyText = body = Object.prototype.toString.call(body); - } + public static func JAVASCRIPT_UTIL_VAR_NAME() -> String { + return "window.\(get_JAVASCRIPT_BRIDGE_NAME())._Util" + } - this.blob = function () { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob); - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])); - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob'); - } else { - return Promise.resolve(new Blob([this._bodyText])); + /* + https://github.com/github/fetch/blob/master/fetch.js + */ + public static func UTIL_JS_SOURCE() -> String { + return """ + \(JAVASCRIPT_UTIL_VAR_NAME()) = { + support: { + searchParams: 'URLSearchParams' in window, + iterable: 'Symbol' in window && 'iterator' in Symbol, + blob: + 'FileReader' in window && + 'Blob' in window && + (function() { + try { + new Blob(); + return true; + } catch (e) { + return false; + } + })(), + formData: 'FormData' in window, + arrayBuffer: 'ArrayBuffer' in window + }, + isDataView: function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj); + }, + fileReaderReady: function(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }); + }, + readBlobAsArrayBuffer: function(blob) { + var reader = new FileReader(); + var promise = \(JAVASCRIPT_UTIL_VAR_NAME()).fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise; + }, + convertBodyToArrayBuffer: function(body) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + var isArrayBufferView = null; + if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer) { + isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + }; + } + + var bodyUsed = false; + + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME()).isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + this.blob = function () { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob); + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])); + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob'); + } else { + return Promise.resolve(new Blob([this._bodyText])); + } + }; + + if (this._bodyArrayBuffer) { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ); + } else { + return Promise.resolve(this._bodyArrayBuffer); + } + } + return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME()).readBlobAsArrayBuffer); + }, + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + convertBodyRequest: function(body) { + if (body == null) { + return new Promise((resolve, reject) => resolve(null)); + } + if (\(JAVASCRIPT_UTIL_VAR_NAME()).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && body instanceof URLSearchParams)) { + return new Promise((resolve, reject) => resolve(body.toString())); + } + if (window.Response != null) { + return new Response(body).arrayBuffer().then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + } + return \(JAVASCRIPT_UTIL_VAR_NAME()).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + }, + arrayBufferToString: function(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') >= 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.substr(2, 40); + return 'multipart/form-data; boundary=' + boundary; + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + for (var header of headers.keys()) { + var value = headers.get(header); + headersObj[header] = value; + } + return headersObj; + }, + convertJsonToHeaders: function(headersJson) { + return new Headers(headersJson); + }, + 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; } }; - - if (this._bodyArrayBuffer) { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (ArrayBuffer.isView(this._bodyArrayBuffer)) { - return Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer.byteOffset, - this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength - ) - ); - } else { - return Promise.resolve(this._bodyArrayBuffer); - } - } - return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer); - }, - isString: function(variable) { - return typeof variable === 'string' || variable instanceof String; - }, - convertBodyRequest: function(body) { - if (body == null) { - return new Promise((resolve, reject) => resolve(null)); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) { - return new Promise((resolve, reject) => resolve(body.toString())); - } - if (window.Response != null) { - return new Response(body).arrayBuffer().then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - } - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - }, - arrayBufferToString: function(arrayBuffer) { - var uint8Array = new Uint8Array(arrayBuffer); - return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); - }, - isBodyFormData: function(bodyString) { - return bodyString.indexOf('------WebKitFormBoundary') >= 0; - }, - getFormDataContentType: function(bodyString) { - var boundary = bodyString.substr(2, 40); - return 'multipart/form-data; boundary=' + boundary; - }, - convertHeadersToJson: function(headers) { - var headersObj = {}; - for (var header of headers.keys()) { - var value = headers.get(header); - headersObj[header] = value; - } - return headersObj; - }, - convertJsonToHeaders: function(headersJson) { - return new Headers(headersJson); - }, - 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; + """ } -}; -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift index ef2e7d677..9cdcdd5a5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift @@ -7,56 +7,65 @@ import Foundation -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT" - -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null; -window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null; -(function() { - document.addEventListener('touchstart', function(event) { - var target = event.target; - while (target) { - if (target.tagName === 'IMG') { - var img = target; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = { - url: img.src - }; - var parent = img.parentNode; - while (parent) { - if (parent.tagName === 'A') { - window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = { - title: parent.textContent, - url: parent.href, - src: img.src +public class LastTouchedAnchorOrImageJS { + + public static let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = null; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = null; + (function() { + document.addEventListener('touchstart', function(event) { + var target = event.target; + while (target) { + if (target.tagName === 'IMG') { + var img = target; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = { + url: img.src + }; + var parent = img.parentNode; + while (parent) { + if (parent.tagName === 'A') { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = { + title: parent.textContent, + url: parent.href, + src: img.src + }; + break; + } + parent = parent.parentNode; + } + return; + } else if (target.tagName === 'A') { + var link = target; + var images = link.getElementsByTagName('img'); + var img = (images.length > 0) ? images[0] : null; + var imgSrc = (img != null) ? img.src : null; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = { + title: link.textContent, + url: link.href, + src: imgSrc }; - break; + return; } - parent = parent.parentNode; + target = target.parentNode; } - return; - } else if (target.tagName === 'A') { - var link = target; - var images = link.getElementsByTagName('img'); - var img = (images.length > 0) ? images[0] : null; - var imgSrc = (img != null) ? img.src : null; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = { - title: link.textContent, - url: link.href, - src: imgSrc - }; - return; - } - target = target.parentNode; - } - }); -})(); -""" + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift index 828de9ef5..18a5e496b 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift @@ -7,33 +7,44 @@ import Foundation -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource" - -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_LOAD_RESOURCE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_LOAD_RESOURCE_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true; -(function() { - var observer = new PerformanceObserver(function(list) { - list.getEntries().forEach(function(entry) { - if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) { - var resource = { - "url": entry.name, - "initiatorType": entry.initiatorType, - "startTime": entry.startTime, - "duration": entry.duration - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource); - } - }); - }); - observer.observe({entryTypes: ['resource']}); -})(); -""" +public class OnLoadResourceJS { + + public static let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnLoadResource" + } + + public static func ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_LOAD_RESOURCE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) = true; + (function() { + var observer = new PerformanceObserver(function(list) { + list.getEntries().forEach(function(entry) { + if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == true) { + var resource = { + "url": entry.name, + "initiatorType": entry.initiatorType, + "startTime": entry.startTime, + "duration": entry.duration + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onLoadResource", resource); + } + }); + }); + observer.observe({entryTypes: ['resource']}); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift index 345b5eced..d9221d10e 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_WINDOW_BLUR_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('blur', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur'); - }); -})(); -""" +public class OnWindowBlurEventJS { + + public static let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_BLUR_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('blur', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowBlur'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift index 9ebda3e96..b08bb0889 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('focus', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus'); - }); -})(); -""" +public class OnWindowFocusEventJS { + + public static let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_FOCUS_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('focus', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowFocus'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift index a1c6637d7..d84c14ee3 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift @@ -7,25 +7,34 @@ import Foundation -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = ""; -(function() { - var metaTagNodes = document.head.getElementsByTagName('meta'); - for (var i = 0; i < metaTagNodes.length; i++) { - var metaTagNode = metaTagNodes[i]; - if (metaTagNode.name === "viewport") { - window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content; - } +public class OriginalViewPortMetaTagContentJS { + + public static let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE(), + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = ""; + (function() { + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + if (metaTagNode.name === "viewport") { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = metaTagNode.content; + } + } + })(); + """ } -})(); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift index 5ac12691f..fbf29f50f 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift @@ -7,18 +7,26 @@ import Foundation -let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" - -let PRINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PRINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let PRINT_JS_SOURCE = """ -window.print = function() { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href); +public class PrintJS { + + public static let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" + + public static func PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PRINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func PRINT_JS_SOURCE() -> String { + return """ + window.print = function() { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onPrintRequest", window.location.href); + } + """ + } } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift index d2ef3e2a5..27c076be2 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift @@ -8,20 +8,25 @@ import Foundation import WebKit -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT" - -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PROMISE_POLYFILL_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -// https://github.com/tildeio/rsvp.js -let PROMISE_POLYFILL_JS_SOURCE = """ -if (window.Promise == null) { - !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PROMISE_POLYFILL_JS_SOURCE, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + // https://github.com/tildeio/rsvp.js + public static let PROMISE_POLYFILL_JS_SOURCE = """ + if (window.Promise == null) { + !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: NOT_SUPPORT_ZOOM_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let NOT_SUPPORT_ZOOM_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func SUPPORT_ZOOM_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift index 184458413..e41494d88 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift @@ -7,4 +7,10 @@ import Foundation -let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels" +public class WebMessageChannelJS { + + public static func WEB_MESSAGE_CHANNELS_VARIABLE_NAME() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._webMessageChannels" + } + +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift index ab8edfaed..6740765ac 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift @@ -7,110 +7,37 @@ import Foundation -let WEB_MESSAGE_LISTENER_JS_SOURCE = """ -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.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); -}; -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); - } -}; - -window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) { - // replace ipv4 address if any - var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); - if (ipv4) { - ip_string = ipv4[1]; - ipv4 = ipv4[2].match(/[0-9]+/g); - for (var i = 0;i < 4;i ++) { - var byte = parseInt(ipv4[i],10); - ipv4[i] = ("0" + byte.toString(16)).substr(-2); - } - ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; - } - - // take care of leading and trailing :: - ip_string = ip_string.replace(/^:|:$/g, ''); - - var ipv6 = ip_string.split(':'); - - for (var i = 0; i < ipv6.length; i ++) { - var hex = ipv6[i]; - if (hex != "") { - // normalize leading zeros - ipv6[i] = ("0000" + hex).substr(-4); - } - else { - // normalize grouped zeros :: - hex = []; - for (var j = ipv6.length; j <= 8; j ++) { - hex.push('0000'); +public class WebMessageListenerJS { + + public static func WEB_MESSAGE_LISTENER_JS_SOURCE() -> String { + return """ + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessageListenerPostMessageReceived', {jsObjectName: this.jsObjectName, message: message}); + }; + FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { + if (listener == null) { + return; } - ipv6[i] = hex.join(':'); - } - } - - return ipv6.join(':'); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) { - for (var rule of allowedOriginRules) { - if (rule === "*") { - return true; - } - if (scheme == null || scheme === "") { - continue; - } - if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { - continue; - } - var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; - var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; - var IPv6 = null; - if (rule.host != null && rule.host[0] === "[") { - try { - IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); - } catch {} - } - var hostIPv6 = null; - try { - hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host); - } catch {} - - var schemeAllowed = scheme == rule.scheme; - - var hostAllowed = rule.host == null || - rule.host === "" || - host === rule.host || - (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || - (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); - - var portAllowed = rulePort === currentPort; - - if (schemeAllowed && hostAllowed && portAllowed) { - return true; - } + 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); + } + }; + """ } - return false; } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift index 8b3a1879f..173167416 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift @@ -7,13 +7,20 @@ import Foundation -let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" - -let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId" - -let WINDOW_ID_INITIALIZE_JS_SOURCE = """ -(function() { - \(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); - return \(WINDOW_ID_VARIABLE_JS_SOURCE); -})() -""" +public class WindowIdJS { + + public static let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" + + public static func WINDOW_ID_VARIABLE_JS_SOURCE() -> String { + return "window._\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_windowId" + } + + public static func WINDOW_ID_INITIALIZE_JS_SOURCE() -> String { + return """ + (function() { + \(WINDOW_ID_VARIABLE_JS_SOURCE()) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); + return \(WINDOW_ID_VARIABLE_JS_SOURCE()); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift index 337fae3a7..6309353fc 100644 --- a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift +++ b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift @@ -63,6 +63,7 @@ public class PrintJobChannelDelegate: ChannelDelegate { } deinit { + debugPrint("PrintJobChannelDelegate - dealloc") dispose() } } diff --git a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift index 6b4c3210e..1e0ce08eb 100644 --- a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift +++ b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift @@ -36,11 +36,9 @@ public class PrintJobController: NSObject, Disposable, UIPrintInteractionControl self.printFormatter = job?.printFormatter self.printPageRenderer = job?.printPageRenderer self.job?.delegate = self - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) } public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) { @@ -99,4 +97,8 @@ public class PrintJobController: NSObject, Disposable, UIPrintInteractionControl plugin?.printJobManager?.jobs[id] = nil plugin = nil } + + deinit { + debugPrint("PrintJobController - dealloc") + } } diff --git a/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift b/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift new file mode 100644 index 000000000..5c2dade06 --- /dev/null +++ b/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift @@ -0,0 +1,247 @@ +// +// ProxyManager.swift +// flutter_inappwebview +// + +import Foundation +import WebKit + +@available(iOS 17.0, *) +public class ProxyManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_proxycontroller" + + private var plugin: SwiftFlutterPlugin? + + init(plugin: SwiftFlutterPlugin) { + super.init(channel: FlutterMethodChannel(name: ProxyManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) + self.plugin = plugin + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? [String: Any] + switch call.method { + case "setProxyOverride": + if let args = arguments?["settings"] as? [String:Any?], + let settings = ProxySettings.fromMap(map: args) { + setProxyOverride(settings) + result(true) + } else { + result(false) + } + break + case "clearProxyOverride": + clearProxyOverride() + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func setProxyOverride(_ settings: ProxySettings) { + let proxyConfigurations = settings.toProxyConfigurations() + WKWebsiteDataStore.default().proxyConfigurations = proxyConfigurations + WKWebsiteDataStore.nonPersistent().proxyConfigurations = proxyConfigurations + } + + public func clearProxyOverride() { + WKWebsiteDataStore.default().proxyConfigurations = [] + WKWebsiteDataStore.nonPersistent().proxyConfigurations = [] + } + + public override func dispose() { + super.dispose() + plugin = nil + } + + deinit { + debugPrint("ProxyManager - dealloc") + dispose() + } +} + +@available(iOS 17.0, *) +public class ProxySettings { + var proxyRules: [ProxyRule] + + init( + proxyRules: [ProxyRule] + ) { + self.proxyRules = proxyRules + } + + public static func fromMap(map: [String:Any?]?) -> ProxySettings? { + guard let map = map else { + return nil + } + return ProxySettings( + proxyRules: (map["proxyRules"] as! [[String:Any?]]).map { ProxyRule.fromMap(map: $0)! } + ) + } + + public func toProxyConfigurations() -> [ProxyConfiguration] { + var proxyConfigurations: [ProxyConfiguration] = [] + for rule in proxyRules { + if let proxyConfiguration = rule.toProxyConfiguration() { + proxyConfigurations.append(proxyConfiguration) + } + } + return proxyConfigurations + } +} + +@available(iOS 17.0, *) +public class ProxyRule { + var url: String + var allowFailover: Bool? + var excludedDomains: [String]? + var matchDomains: [String]? + var username: String? + var password: String? + var relayHop1: ProxyRelayHop? + var relayHop2: ProxyRelayHop? + + init( + url: String, + allowFailover: Bool?, + excludedDomains: [String]?, + matchDomains: [String]?, + username: String?, + password: String?, + relayHop1: ProxyRelayHop?, + relayHop2: ProxyRelayHop? + ) { + self.url = url + self.allowFailover = allowFailover + self.excludedDomains = excludedDomains + self.matchDomains = matchDomains + self.username = username + self.password = password + self.relayHop1 = relayHop1 + self.relayHop2 = relayHop2 + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRule? { + guard let map = map else { + return nil + } + return ProxyRule( + url: map["url"] as! String, + allowFailover: map["allowFailover"] as? Bool, + excludedDomains: map["excludedDomains"] as? [String], + matchDomains: map["matchDomains"] as? [String], + username: map["username"] as? String, + password: map["password"] as? String, + relayHop1: ProxyRelayHop.fromMap(map: map["relayHop1"] as? [String:Any?]), + relayHop2: ProxyRelayHop.fromMap(map: map["relayHop2"] as? [String:Any?]) + ) + } + + public func toProxyConfiguration() -> ProxyConfiguration? { + guard let endpointUrl = URL(string: url.contains("://") ? url : "http://" + url), + let port: NWEndpoint.Port = .init(rawValue: UInt16(endpointUrl.port ?? 80)), + let host = endpointUrl.host else { + return nil + } + + var endpointHost = NWEndpoint.Host(host) + if let ipv4 = IPv4Address(host) { + endpointHost = .ipv4(ipv4) + } else if let ipv6 = IPv6Address(host) { + endpointHost = .ipv6(ipv6) + } + let endpoint = NWEndpoint.hostPort(host: endpointHost, port: port) + var proxyConfiguration: ProxyConfiguration + let proxyRelayHops: [ProxyRelayHop] = [relayHop1, relayHop2].filter({ $0 != nil }).map({ $0! }) + if !proxyRelayHops.isEmpty { + proxyConfiguration = ProxyConfiguration(relayHops: proxyRelayHops.compactMap({ $0.toRelayHop() })) + } else { + proxyConfiguration = endpointUrl.scheme?.lowercased() == "socks5" ? + ProxyConfiguration(socksv5Proxy: endpoint) : + ProxyConfiguration(httpCONNECTProxy: endpoint, tlsOptions: endpointUrl.scheme?.lowercased() == "https" ? .init() : nil) + } + + if let allowFailover = allowFailover { + proxyConfiguration.allowFailover = allowFailover + } + if let excludedDomains = excludedDomains { + proxyConfiguration.excludedDomains = excludedDomains + } + if let matchDomains = matchDomains { + proxyConfiguration.matchDomains = matchDomains + } + if let username = username, let password = password { + proxyConfiguration.applyCredential(username: username, password: password) + } + return proxyConfiguration + } +} + +@available(iOS 17.0, *) +public class ProxyRelayHop { + var http3RelayEndpoint: String? + var http2RelayEndpoint: String? + var additionalHTTPHeaders: [String:String]? + + init( + http3RelayEndpoint: String, + http2RelayEndpoint: String?, + additionalHTTPHeaders: [String:String]? + ) { + self.http3RelayEndpoint = http3RelayEndpoint + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + init( + http2RelayEndpoint: String, + additionalHTTPHeaders: [String:String]? + ) { + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRelayHop? { + guard let map = map else { + return nil + } + let http3RelayEndpoint = map["http3RelayEndpoint"] as? String + let http2RelayEndpoint = map["http2RelayEndpoint"] as? String + let additionalHTTPHeaders = map["additionalHTTPHeaders"] as? [String:String] + if http3RelayEndpoint == nil, http2RelayEndpoint == nil { + return nil + } + if http3RelayEndpoint != nil { + return ProxyRelayHop( + http3RelayEndpoint: http3RelayEndpoint!, + http2RelayEndpoint: http2RelayEndpoint, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + return ProxyRelayHop( + http2RelayEndpoint: http2RelayEndpoint!, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + + public func toRelayHop() -> ProxyConfiguration.RelayHop? { + if let http3RelayEndpoint = http3RelayEndpoint, + let url = URL(string: http3RelayEndpoint) { + var http2Endpoint: NWEndpoint? = nil + if let http2RelayEndpoint = http2RelayEndpoint, + let url2 = URL(string: http2RelayEndpoint) { + http2Endpoint = NWEndpoint.url(url2) + } + return ProxyConfiguration.RelayHop(http3RelayEndpoint: NWEndpoint.url(url), + http2RelayEndpoint: http2Endpoint, + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + if let http2RelayEndpoint = http2RelayEndpoint, + let url = URL(string: http2RelayEndpoint) { + return ProxyConfiguration.RelayHop(http2RelayEndpoint: NWEndpoint.url(url), + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + return nil + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift b/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift index 5df23c173..3cf19bfc5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift +++ b/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift @@ -21,11 +21,9 @@ public class PullToRefreshControl: UIRefreshControl, Disposable { super.init() self.plugin = plugin self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) } required init?(coder: NSCoder) { diff --git a/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index 5887ea77e..805d422c9 100755 --- a/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -19,7 +19,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate { var prewarmingTokens: [String: Any?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift b/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift index 980e983e4..e71fa9827 100755 --- a/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift @@ -26,7 +26,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle SafariViewController.prepareConfig(configuration: configuration, safariSettings: safariSettings) super.init(url: url, configuration: configuration) let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel) self.delegate = self } @@ -38,7 +38,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle self.safariSettings = safariSettings super.init(url: url, entersReaderIfAvailable: entersReaderIfAvailable) let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel) self.delegate = self } diff --git a/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift b/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift index 22eaecdcd..99e49ddcb 100755 --- a/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift +++ b/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift @@ -24,7 +24,7 @@ import SafariServices public class SwiftFlutterPlugin: NSObject, FlutterPlugin { - var registrar: FlutterPluginRegistrar? + var registrar: FlutterPluginRegistrar var platformUtil: PlatformUtil? var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? @@ -35,14 +35,16 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var chromeSafariBrowserManager: ChromeSafariBrowserManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? var printJobManager: PrintJobManager? + var proxyManager: Any? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] public init(with registrar: FlutterPluginRegistrar) { + self.registrar = registrar + super.init() - self.registrar = registrar registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) @@ -59,6 +61,9 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) printJobManager = PrintJobManager(plugin: self) + if #available(iOS 17.0, *) { + proxyManager = ProxyManager(plugin: self) + } } public static func register(with registrar: FlutterPluginRegistrar) { @@ -79,16 +84,20 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { credentialDatabase?.dispose() credentialDatabase = nil if #available(iOS 11.0, *) { - (myCookieManager as! MyCookieManager?)?.dispose() + (myCookieManager as? MyCookieManager)?.dispose() myCookieManager = nil } if #available(iOS 9.0, *) { - (myWebStorageManager as! MyWebStorageManager?)?.dispose() + (myWebStorageManager as? MyWebStorageManager)?.dispose() myWebStorageManager = nil } webAuthenticationSessionManager?.dispose() webAuthenticationSessionManager = nil printJobManager?.dispose() printJobManager = nil + if #available(iOS 17.0, *) { + (proxyManager as? ProxyManager)?.dispose() + proxyManager = nil + } } } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift b/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift new file mode 100644 index 000000000..71334a106 --- /dev/null +++ b/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift @@ -0,0 +1,42 @@ +// +// JavaScriptHandlerFunctionData.swift +// Pods +// +// Created by Lorenzo Pichilli on 27/10/24. +// + +public class JavaScriptHandlerFunctionData: NSObject { + var args: String + var isMainFrame: Bool + var origin: String + var requestUrl: String + + public init(args: String, isMainFrame: Bool, origin: String, requestUrl: String) { + self.args = args + self.isMainFrame = isMainFrame + self.origin = origin + self.requestUrl = requestUrl + } + + public static func fromMap(map: [String:Any?]?) -> JavaScriptHandlerFunctionData? { + guard let map = map else { + return nil + } + + return JavaScriptHandlerFunctionData( + args: map["args"] as! String, + isMainFrame: map["isMainFrame"] as! Bool, + origin: map["origin"] as! String, + requestUrl: map["requestUrl"] as! String + ) + } + + public func toMap () -> [String:Any?] { + return [ + "args": args, + "isMainFrame": isMainFrame, + "origin": origin, + "requestUrl": requestUrl + ] + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift b/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift index 9b6499f24..591702741 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift @@ -16,8 +16,8 @@ public class PluginScript: UserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -36,8 +36,9 @@ public class PluginScript: UserScript { } @available(iOS 14.0, *) - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, + allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -47,6 +48,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, requiredInAllContentWorlds: Bool? = nil, + allowedOriginRules: [String]? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { if #available(iOS 14.0, *) { return PluginScript( @@ -55,6 +57,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -64,6 +67,7 @@ public class PluginScript: UserScript { source: source ?? self.source, injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -75,6 +79,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, contentWorld: WKContentWorld? = nil, + allowedOriginRules: [String]? = nil, requiredInAllContentWorlds: Bool? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { return PluginScript( @@ -83,6 +88,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: contentWorld ?? self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -95,6 +101,7 @@ public class PluginScript: UserScript { lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && lhs.contentWorld == rhs.contentWorld && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } else { @@ -102,6 +109,7 @@ public class PluginScript: UserScript { lhs.source == rhs.source && lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift b/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift index e18c588de..c12160cb5 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift @@ -10,6 +10,7 @@ import WebKit public class UserScript: WKUserScript { var groupName: String? + var allowedOriginRules: [String]? private var contentWorldWrapper: Any? @available(iOS 14.0, *) @@ -27,9 +28,10 @@ public class UserScript: WKUserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) self.groupName = groupName + self.allowedOriginRules = allowedOriginRules } @available(iOS 14.0, *) @@ -39,10 +41,34 @@ public class UserScript: WKUserScript { } @available(iOS 14.0, *) - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) self.groupName = groupName self.contentWorld = contentWorld + self.allowedOriginRules = allowedOriginRules + } + + static private func wrapSourceCodeAddChecks(source: String, allowedOriginRules: [String]?) -> String { + var ifStatement = "if (" + if let allowedOriginRules = allowedOriginRules, !allowedOriginRules.contains("*") { + if allowedOriginRules.isEmpty { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return "" + } + var jsRegExpArray = "[" + for allowedOriginRule in allowedOriginRules { + if jsRegExpArray.count > 1 { + jsRegExpArray += "," + } + jsRegExpArray += "new RegExp('\(allowedOriginRule.replacingOccurrences(of: "\'", with: "\\'"))')" + } + if jsRegExpArray.count > 1 { + jsRegExpArray += "]" + ifStatement += "\(jsRegExpArray).some(function(rx) { return rx.test(window.location.origin); })" + } + } + return ifStatement.count > 4 ? "\(ifStatement)) { \(source) }" : source } public static func fromMap(map: [String:Any?]?, windowId: Int64?) -> UserScript? { @@ -58,14 +84,16 @@ public class UserScript: WKUserScript { source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, forMainFrameOnly: map["forMainFrameOnly"] as! Bool, - in: contentWorld + in: contentWorld, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } return UserScript( groupName: map["groupName"] as? String, source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, - forMainFrameOnly: map["forMainFrameOnly"] as! Bool + forMainFrameOnly: map["forMainFrameOnly"] as! Bool, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift b/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift index 633f1e1b5..c6c78b097 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift @@ -166,7 +166,7 @@ extension WKUserContentController { } } if let windowId = contentWorld.windowId { - generatedCode += "\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(String(windowId));\n" + generatedCode += "\(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()) = \(String(windowId));\n" } return generatedCode + "\n" + source } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift b/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift index b7a6e53ca..7cb2f3b58 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift @@ -33,10 +33,10 @@ public class WebMessagePort: NSObject { let index = name == "port1" ? 0 : 1 webView.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).onmessage = function (event) { - window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessagePortMessageReceived', { "webMessageChannelId": "\(webMessageChannel.id)", "index": \(String(index)), "message": { @@ -74,14 +74,14 @@ public class WebMessagePort: NSObject { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString)); } @@ -104,7 +104,7 @@ public class WebMessagePort: NSObject { if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView { let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).close(); } diff --git a/flutter_inappwebview_ios/ios/Classes/Util.swift b/flutter_inappwebview_ios/ios/Classes/Util.swift index 9992ece9e..25be4af12 100644 --- a/flutter_inappwebview_ios/ios/Classes/Util.swift +++ b/flutter_inappwebview_ios/ios/Classes/Util.swift @@ -12,16 +12,16 @@ var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] public class Util { public static func getUrlAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> URL { - guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), - let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { + let key = plugin.registrar.lookupKey(forAsset: assetFilePath) + guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetURL } public static func getAbsPathAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> String { - guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), - let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { + let key = plugin.registrar.lookupKey(forAsset: assetFilePath) + guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetAbsPath diff --git a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index 99409b11d..3f8c32f6d 100644 --- a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -37,7 +37,7 @@ public class WebAuthenticationSession: NSObject, ASWebAuthenticationPresentation self.session = SFAuthenticationSession(url: self.url, callbackURLScheme: self.callbackURLScheme, completionHandler: self.completionHandler) } let channel = FlutterMethodChannel(name: WebAuthenticationSession.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = WebAuthenticationSessionChannelDelegate(webAuthenticationSession: self, channel: channel) } diff --git a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 6ba8238d5..66eff8523 100644 --- a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -18,7 +18,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Resources/PrivacyInfo.xcprivacy b/flutter_inappwebview_ios/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..0eca193ea --- /dev/null +++ b/flutter_inappwebview_ios/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + \ No newline at end of file diff --git a/flutter_inappwebview_ios/ios/flutter_inappwebview_ios.podspec b/flutter_inappwebview_ios/ios/flutter_inappwebview_ios.podspec index a08fa7fbb..d0aec2684 100755 --- a/flutter_inappwebview_ios/ios/flutter_inappwebview_ios.podspec +++ b/flutter_inappwebview_ios/ios/flutter_inappwebview_ios.podspec @@ -17,6 +17,7 @@ A new Flutter plugin. s.resources = 'Storyboards/**/*.storyboard' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' + s.resource_bundles = {'flutter_inappwebview_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } @@ -29,12 +30,12 @@ A new Flutter plugin. s.swift_version = '5.0' - s.platforms = { :ios => '11.0' } - s.dependency 'OrderedSet', '~>5.0' + s.platforms = { :ios => '12.0' } + s.dependency 'OrderedSet', '~>6.0.3' s.default_subspec = 'Core' s.subspec 'Core' do |core| - core.platform = :ios, '9.0' + core.platform = :ios, '12.0' end end diff --git a/flutter_inappwebview_ios/lib/src/cookie_manager.dart b/flutter_inappwebview_ios/lib/src/cookie_manager.dart index e640c9f02..2c0eea9cd 100755 --- a/flutter_inappwebview_ios/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_ios/lib/src/cookie_manager.dart @@ -407,13 +407,11 @@ class IOSCookieManager extends PlatformCookieManager with ChannelController { Future _getCookieExpirationDate(int expiresDate) async { var platformUtil = PlatformUtil.instance(); var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc(); - return !kIsWeb - ? await platformUtil.formatDate( - date: dateTime, - format: 'EEE, dd MMM yyyy hh:mm:ss z', - locale: 'en_US', - timezone: 'GMT') - : await platformUtil.getWebCookieExpirationDate(date: dateTime); + return await platformUtil.formatDate( + date: dateTime, + format: 'EEE, dd MMM yyyy HH:mm:ss z', + locale: 'en_US', + timezone: 'GMT'); } Future _shouldUseJavascript() async { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart index 0e9da6539..174329328 100644 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart @@ -32,8 +32,10 @@ class IOSHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -150,6 +152,7 @@ class IOSHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -359,13 +362,23 @@ class IOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = 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) { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart index 235d4ed17..e2b9d622d 100755 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; -import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// @@ -36,8 +35,10 @@ class IOSInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -160,6 +161,7 @@ class IOSInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -363,15 +365,24 @@ class IOSInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + 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) { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart index 7e847f14f..f807fb8ff 100644 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,21 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [IOSInAppWebViewController]. /// @@ -66,8 +48,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController 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 _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -359,11 +340,11 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -371,20 +352,25 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1404,17 +1390,22 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1436,7 +1427,8 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1453,43 +1445,46 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxProgress(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1515,7 +1510,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1523,7 +1518,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1535,7 +1530,19 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + 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"'); @@ -1975,16 +1982,14 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2234,6 +2239,30 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('clearFocus', args); } + @override + Future setInputMethodEnabled(bool enabled) async { + Map args = {}; + args.putIfAbsent("enabled", () => enabled); + return await channel?.invokeMethod('setInputMethodEnabled', args); + } + + @override + Future hideInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('hideInputMethod', args); + } + + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + @override Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; @@ -2682,6 +2711,19 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController await channel?.invokeMethod('loadSimulatedRequest', 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 getDefaultUserAgent() async { Map args = {}; @@ -2712,6 +2754,23 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController 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) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart index 504f1f1aa..91139fd42 100644 --- a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart @@ -11,6 +11,7 @@ import 'pull_to_refresh/main.dart'; import 'web_message/main.dart'; import 'web_storage/main.dart'; import 'web_authentication_session/main.dart'; +import 'proxy_controller.dart'; /// Implementation of [InAppWebViewPlatform] using the WebKit API. class IOSInAppWebViewPlatform extends InAppWebViewPlatform { @@ -150,7 +151,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [IOSWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override IOSWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -161,7 +162,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [IOSLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override IOSLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -172,7 +173,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override IOSSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, @@ -270,4 +271,13 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { IOSWebAuthenticationSession createPlatformWebAuthenticationSessionStatic() { return IOSWebAuthenticationSession.static(); } + + /// Creates a new [IOSProxyController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [ProxyController] in `flutter_inappwebview` instead. + @override + PlatformProxyController createPlatformProxyController(PlatformProxyControllerCreationParams params) { + return IOSProxyController(params); + } } diff --git a/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart index e2385f9c3..f817d8501 100644 --- a/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class IOSPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [IOSPrintJobControllerCreationParams] instance. const IOSPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [IOSPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory IOSPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class IOSPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return IOSPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -35,7 +35,6 @@ class IOSPrintJobController extends PlatformPrintJobController : IOSPrintJobControllerCreationParams .fromPlatformPrintJobControllerCreationParams(params), ) { - onComplete = params.onComplete; channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); handler = _handleMethod; diff --git a/flutter_inappwebview_ios/lib/src/proxy_controller.dart b/flutter_inappwebview_ios/lib/src/proxy_controller.dart new file mode 100644 index 000000000..a94bdef2d --- /dev/null +++ b/flutter_inappwebview_ios/lib/src/proxy_controller.dart @@ -0,0 +1,81 @@ +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 [IOSProxyController]. +/// +/// 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 IOSProxyControllerCreationParams + extends PlatformProxyControllerCreationParams { + /// Creates a new [IOSProxyControllerCreationParams] instance. + const IOSProxyControllerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformProxyControllerCreationParams params, + ) : super(); + + /// Creates a [IOSProxyControllerCreationParams] instance based on [PlatformProxyControllerCreationParams]. + factory IOSProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + PlatformProxyControllerCreationParams params) { + return IOSProxyControllerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformProxyController} +class IOSProxyController extends PlatformProxyController + with ChannelController { + /// Creates a new [IOSProxyController]. + IOSProxyController(PlatformProxyControllerCreationParams params) + : super.implementation( + params is IOSProxyControllerCreationParams + ? params + : IOSProxyControllerCreationParams + .fromPlatformProxyControllerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller'); + handler = handleMethod; + initMethodCallHandler(); + } + + static IOSProxyController? _instance; + + ///Gets the [IOSProxyController] shared instance. + static IOSProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static IOSProxyController _init() { + _instance = IOSProxyController(IOSProxyControllerCreationParams( + const PlatformProxyControllerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @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); + } + + @override + void dispose() { + // empty + } +} + +extension InternalProxyController on IOSProxyController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart index b16b3b75a..62bf40117 100644 --- a/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class IOSWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_ios/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_ios/lib/src/web_storage/web_storage.dart index 462a77819..ca5accd3c 100644 --- a/flutter_inappwebview_ios/lib/src/web_storage/web_storage.dart +++ b/flutter_inappwebview_ios/lib/src/web_storage/web_storage.dart @@ -70,7 +70,7 @@ class IOSStorageCreationParams extends PlatformStorageCreationParams { } ///{@macro flutter_inappwebview_platform_interface.PlatformStorage} -abstract class IOSStorage implements PlatformStorage { +abstract mixin class IOSStorage implements PlatformStorage { @override IOSInAppWebViewController? controller; diff --git a/flutter_inappwebview_ios/pubspec.yaml b/flutter_inappwebview_ios/pubspec.yaml index 73c6d29c4..6cda6880a 100644 --- a/flutter_inappwebview_ios/pubspec.yaml +++ b/flutter_inappwebview_ios/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview_ios description: iOS implementation of the flutter_inappwebview plugin. -version: 1.0.13 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_ios issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,19 +14,20 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.0.10 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 - plugin_platform_interface: ^2.0.2 + flutter_lints: ^4.0.0 + plugin_platform_interface: ^2.1.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter_inappwebview_macos/CHANGELOG.md b/flutter_inappwebview_macos/CHANGELOG.md index 24521046d..825781e37 100644 --- a/flutter_inappwebview_macos/CHANGELOG.md +++ b/flutter_inappwebview_macos/CHANGELOG.md @@ -1,3 +1,55 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Fixed internal javascript callback handlers when the WebView has windowId not null +- Fixed crash of unhandled `onPrintRequest` WebView event +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `alpha` property of `InAppWebViewSettings` + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Implemented `requestFocus`, `clearFocus` WebView methods +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Added support for `UserScript.allowedOriginRules` parameter +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- Implemented workaround for "[macOS] Copy Shortcut does not work if TextField outside of WebView has focus" [#2380](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380) + +## 1.1.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.3.0 + +## 1.1.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.2.0 + +## 1.1.0+3 + +- Updated flutter_inappwebview_platform_interface version + +## ## 1.1.0+2 + +- Updated pubspec.yaml + +## 1.1.0+1 + +- Fixed "v6.1.0 fails to compile on Xcode 15" [#2288](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2288) + +## 1.1.0 + +- Added `InAppWebView` support +- Added privacy manifest +- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. +- Fixed "[MACOS] launching InAppBrowser with 'hidden: true' calls onExit immediately" [#1939](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939) +- Fixed XCode 16 build + ## 1.0.11 - Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10` diff --git a/flutter_inappwebview_macos/example/pubspec.lock b/flutter_inappwebview_macos/example/pubspec.lock index 6c96e4546..85b83c9fc 100644 --- a/flutter_inappwebview_macos/example/pubspec.lock +++ b/flutter_inappwebview_macos/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -79,25 +79,24 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_macos: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.0.11" + version: "1.2.0-beta.3" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" - url: "https://pub.dev" - source: hosted - version: "1.0.10" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -121,6 +120,30 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -133,58 +156,58 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" platform: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" process: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -242,10 +265,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -258,26 +281,18 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 - url: "https://pub.dev" - source: hosted - version: "11.10.0" - web: - dependency: transitive - description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "14.2.5" webdriver: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_inappwebview_macos/lib/src/cookie_manager.dart b/flutter_inappwebview_macos/lib/src/cookie_manager.dart index bf9d5a9bc..6078cb526 100755 --- a/flutter_inappwebview_macos/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_macos/lib/src/cookie_manager.dart @@ -407,13 +407,11 @@ class MacOSCookieManager extends PlatformCookieManager with ChannelController { Future _getCookieExpirationDate(int expiresDate) async { var platformUtil = PlatformUtil.instance(); var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc(); - return !kIsWeb - ? await platformUtil.formatDate( - date: dateTime, - format: 'EEE, dd MMM yyyy hh:mm:ss z', - locale: 'en_US', - timezone: 'GMT') - : await platformUtil.getWebCookieExpirationDate(date: dateTime); + return await platformUtil.formatDate( + date: dateTime, + format: 'EEE, dd MMM yyyy HH:mm:ss z', + locale: 'en_US', + timezone: 'GMT'); } Future _shouldUseJavascript() async { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart index 23055add4..eb4911ef7 100644 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart @@ -31,8 +31,10 @@ class MacOSHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -149,6 +151,7 @@ class MacOSHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -353,13 +356,24 @@ class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = 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) { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart index 90fb8fc34..98f8b15bc 100755 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; +import 'headless_in_app_webview.dart'; import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. @@ -34,8 +34,10 @@ class MacOSInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -158,6 +160,7 @@ class MacOSInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -300,7 +303,7 @@ class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { } } - return UiKitView( + return AppKitView( viewType: 'com.pichillilorenzo/flutter_inappwebview', onPlatformViewCreated: _onPlatformViewCreated, gestureRecognizers: params.gestureRecognizers, @@ -356,15 +359,24 @@ class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + 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) { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart index 38ab1bc76..428dbe9dc 100644 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,20 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; - import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [MacOSInAppWebViewController]. /// @@ -66,8 +47,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController 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 _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -209,6 +189,13 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController } Future _handleMethod(MethodCall call) async { + if (call.method == "_onMouseDown") { + // Workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380 + // TODO: remove when Flutter fixes this + FocusManager.instance.primaryFocus?.unfocus(); + return; + } + if (PlatformInAppWebViewController.debugLoggingSettings.enabled && call.method != "onCallJsHandler") { _debugLog(call.method, call.arguments); @@ -360,11 +347,11 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -372,20 +359,25 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1405,17 +1397,22 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1437,7 +1434,8 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1454,43 +1452,46 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxProgress(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1516,7 +1517,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1524,7 +1525,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1536,7 +1537,19 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + 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"'); @@ -1976,16 +1989,14 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2204,6 +2215,23 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('getSelectedText', args); } + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + + @override + Future clearFocus() async { + Map args = {}; + return await channel?.invokeMethod('clearFocus', args); + } + @override Future> getMetaTags() async { List metaTags = []; @@ -2648,6 +2676,23 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController 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) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart index 3e13e4f5b..d79a74196 100644 --- a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart @@ -9,6 +9,7 @@ import 'print_job/main.dart'; import 'web_message/main.dart'; import 'web_storage/main.dart'; import 'web_authentication_session/main.dart'; +import 'proxy_controller.dart'; /// Implementation of [InAppWebViewPlatform] using the WebKit API. class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { @@ -48,17 +49,16 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { return MacOSInAppWebViewController.static(); } - // TODO: unhide when Flutter official PlatformView for macOS is available - // /// Creates a new [MacOSInAppWebViewWidget]. - // /// - // /// This function should only be called by the app-facing package. - // /// Look at using [InAppWebView] in `flutter_inappwebview` instead. - // @override - // MacOSInAppWebViewWidget createPlatformInAppWebViewWidget( - // PlatformInAppWebViewWidgetCreationParams params, - // ) { - // return MacOSInAppWebViewWidget(params); - // } + /// Creates a new [MacOSInAppWebViewWidget]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + MacOSInAppWebViewWidget createPlatformInAppWebViewWidget( + PlatformInAppWebViewWidgetCreationParams params, + ) { + return MacOSInAppWebViewWidget(params); + } /// Creates a new [MacOSFindInteractionController]. /// @@ -138,7 +138,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [MacOSWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override MacOSWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -149,7 +149,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [MacOSLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override MacOSLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -160,7 +160,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override MacOSSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, @@ -238,4 +238,13 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { MacOSWebAuthenticationSession createPlatformWebAuthenticationSessionStatic() { return MacOSWebAuthenticationSession.static(); } + + /// Creates a new [MacOSProxyController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [ProxyController] in `flutter_inappwebview` instead. + @override + PlatformProxyController createPlatformProxyController(PlatformProxyControllerCreationParams params) { + return MacOSProxyController(params); + } } diff --git a/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart index be3136d6f..adb6df7e7 100644 --- a/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class MacOSPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [MacOSPrintJobControllerCreationParams] instance. const MacOSPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [MacOSPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory MacOSPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class MacOSPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return MacOSPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -35,7 +35,6 @@ class MacOSPrintJobController extends PlatformPrintJobController : MacOSPrintJobControllerCreationParams .fromPlatformPrintJobControllerCreationParams(params), ) { - onComplete = params.onComplete; channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); handler = _handleMethod; diff --git a/flutter_inappwebview_macos/lib/src/proxy_controller.dart b/flutter_inappwebview_macos/lib/src/proxy_controller.dart new file mode 100644 index 000000000..6f0382b90 --- /dev/null +++ b/flutter_inappwebview_macos/lib/src/proxy_controller.dart @@ -0,0 +1,81 @@ +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 [MacOSProxyController]. +/// +/// 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 MacOSProxyControllerCreationParams + extends PlatformProxyControllerCreationParams { + /// Creates a new [MacOSProxyControllerCreationParams] instance. + const MacOSProxyControllerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformProxyControllerCreationParams params, + ) : super(); + + /// Creates a [MacOSProxyControllerCreationParams] instance based on [PlatformProxyControllerCreationParams]. + factory MacOSProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + PlatformProxyControllerCreationParams params) { + return MacOSProxyControllerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformProxyController} +class MacOSProxyController extends PlatformProxyController + with ChannelController { + /// Creates a new [MacOSProxyController]. + MacOSProxyController(PlatformProxyControllerCreationParams params) + : super.implementation( + params is MacOSProxyControllerCreationParams + ? params + : MacOSProxyControllerCreationParams + .fromPlatformProxyControllerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller'); + handler = handleMethod; + initMethodCallHandler(); + } + + static MacOSProxyController? _instance; + + ///Gets the [MacOSProxyController] shared instance. + static MacOSProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static MacOSProxyController _init() { + _instance = MacOSProxyController(MacOSProxyControllerCreationParams( + const PlatformProxyControllerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @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); + } + + @override + void dispose() { + // empty + } +} + +extension InternalProxyController on MacOSProxyController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart index c0682298b..1618486f5 100644 --- a/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class MacOSWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_macos/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_macos/lib/src/web_storage/web_storage.dart index 25299efb7..b8e651797 100644 --- a/flutter_inappwebview_macos/lib/src/web_storage/web_storage.dart +++ b/flutter_inappwebview_macos/lib/src/web_storage/web_storage.dart @@ -70,7 +70,7 @@ class MacOSStorageCreationParams extends PlatformStorageCreationParams { } ///{@macro flutter_inappwebview_platform_interface.PlatformStorage} -abstract class MacOSStorage implements PlatformStorage { +abstract mixin class MacOSStorage implements PlatformStorage { @override MacOSInAppWebViewController? controller; diff --git a/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift b/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift index c11ac05b1..4a7a45df7 100755 --- a/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift +++ b/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift @@ -14,7 +14,7 @@ public class CredentialDatabase: ChannelDelegate { static var credentialStore = URLCredentialStorage.shared init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift b/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift index 68bcae597..f6e5492a8 100644 --- a/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift +++ b/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift @@ -24,11 +24,9 @@ public class FindInteractionController: NSObject, Disposable { self.plugin = plugin self.webView = webView self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) } public func prepare() { @@ -61,7 +59,7 @@ public class FindInteractionController: NSObject, Disposable { } if find != "" { - let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');" + let startSearch = "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync('\(find)');" webView.evaluateJavaScript(startSearch, completionHandler: completionHandler) } } @@ -73,7 +71,7 @@ public class FindInteractionController: NSObject, Disposable { } return } - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) } public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) { @@ -83,7 +81,7 @@ public class FindInteractionController: NSObject, Disposable { } return } - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches();", completionHandler: completionHandler) } public func dispose() { diff --git a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index b95222fb1..9f5b9524f 100644 --- a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -20,7 +20,7 @@ public class HeadlessInAppWebView: Disposable { self.flutterWebView = flutterWebView self.plugin = plugin let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger) + binaryMessenger: plugin.registrar.messenger) self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel) } diff --git a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 29bed518c..4dcd1b536 100644 --- a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -19,7 +19,7 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift index b15564d1b..68745e207 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift @@ -19,7 +19,7 @@ public class InAppBrowserManager: ChannelDelegate { var plugin: InAppWebViewFlutterPlugin? init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } @@ -92,10 +92,13 @@ public class InAppBrowserManager: ChannelDelegate { window.windowController?.showWindow(self) } + window.makeKeyAndOrderFront(self) if browserSettings.hidden { + // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939 + // without calling first window.makeKeyAndOrderFront(self) + // window.hide() would deallocate and dispose the InAppBrowserWindow window.hide() - } else { - window.makeKeyAndOrderFront(self) + NSApplication.shared.mainWindow?.makeKeyAndOrderFront(self) } } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift index 48b16e936..890f154e3 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -33,11 +33,11 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega var isHidden = false public override func loadView() { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger) + let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: plugin.registrar.messenger) channelDelegate = InAppBrowserChannelDelegate(channel: channel) var userScripts: [UserScript] = [] diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift index 9cae7114d..04895703f 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift @@ -9,15 +9,12 @@ import Foundation import WebKit import FlutterMacOS -public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Disposable { +public class FlutterWebViewController: NSView, Disposable { - var myView: NSView? var keepAliveId: String? init(plugin: InAppWebViewFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { - super.init() - - myView = NSView(frame: frame) + super.init(frame: frame) keepAliveId = params["keepAliveId"] as? String @@ -42,17 +39,15 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos webView = webViewTransport.webView webView!.id = viewId webView!.plugin = plugin - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) - } - webView!.frame = myView!.bounds + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: plugin.registrar.messenger) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) + webView!.frame = self.bounds webView!.initialUserScripts = userScripts } else { webView = InAppWebView(id: viewId, plugin: plugin, - frame: myView!.bounds, + frame: self.bounds, configuration: preWebviewConfiguration, userScripts: userScripts) } @@ -64,17 +59,21 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos findInteractionController.prepare() webView!.autoresizingMask = [.width, .height] - myView!.autoresizesSubviews = true - myView!.autoresizingMask = [.width, .height] - myView!.addSubview(webView!) + self.autoresizesSubviews = true + self.autoresizingMask = [.width, .height] + self.addSubview(webView!) webView!.settings = settings webView!.prepare() webView!.windowCreated = true } + required init?(coder nsCoder: NSCoder) { + super.init(coder: nsCoder) + } + public func webView() -> InAppWebView? { - for subview in myView?.subviews ?? [] + for subview in self.subviews { if let item = subview as? InAppWebView { @@ -85,7 +84,7 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos } public func view() -> NSView { - return myView! + return self } public func makeInitialLoad(params: NSDictionary) { @@ -185,9 +184,8 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos } } if removeFromSuperview { - myView?.removeFromSuperview() + self.removeFromSuperview() } - myView = nil } } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewFactory.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewFactory.swift index 5fe111835..d659dc1a2 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewFactory.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewFactory.swift @@ -67,6 +67,6 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { flutterWebView?.makeInitialLoad(params: arguments!) } - return flutterWebView!.view() + return flutterWebView! } } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift index 2ec0c7aae..425ce3551 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift @@ -7,7 +7,7 @@ import FlutterMacOS import Foundation -import WebKit +@preconcurrency import WebKit public class InAppWebView: WKWebView, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, @@ -50,6 +50,11 @@ public class InAppWebView: WKWebView, WKUIDelegate, fileprivate var interceptOnlyAsyncAjaxRequestsPluginScript: PluginScript? + private var exceptedBridgeSecret = NSUUID().uuidString + private var javaScriptBridgeEnabled = true + + public override var acceptsFirstResponder: Bool { return true } + init(id: Any?, plugin: InAppWebViewFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) @@ -118,6 +123,15 @@ public class InAppWebView: WKWebView, WKUIDelegate, // } if let settings = settings { + if let viewAlpha = settings.alpha { + alphaValue = CGFloat(viewAlpha) + } + + javaScriptBridgeEnabled = settings.javaScriptBridgeEnabled + if let javaScriptBridgeOriginAllowList = settings.javaScriptBridgeOriginAllowList, javaScriptBridgeOriginAllowList.isEmpty { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false + } if #available(macOS 12.0, *), settings.transparentBackground { underPageBackgroundColor = .clear @@ -193,56 +207,62 @@ public class InAppWebView: WKWebView, WKUIDelegate, // This is a limitation of the official WebKit API. return } - configuration.userContentController = WKUserContentController() configuration.userContentController.initialize() if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { return } - configuration.userContentController.addPluginScript(PROMISE_POLYFILL_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(CONSOLE_LOG_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(PRINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT) - if let settings = settings { - interceptOnlyAsyncAjaxRequestsPluginScript = createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests) - if settings.useShouldInterceptAjaxRequest { - if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + let pluginScriptsOriginAllowList = settings?.pluginScriptsOriginAllowList + let pluginScriptsForMainFrameOnly = settings?.pluginScriptsForMainFrameOnly ?? true + + let javaScriptBridgeOriginAllowList = settings?.javaScriptBridgeOriginAllowList ?? pluginScriptsOriginAllowList + let javaScriptBridgeForMainFrameOnly = settings?.javaScriptBridgeForMainFrameOnly ?? pluginScriptsForMainFrameOnly + + configuration.userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: exceptedBridgeSecret, allowedOriginRules: javaScriptBridgeOriginAllowList, forMainFrameOnly: javaScriptBridgeForMainFrameOnly)) + configuration.userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindElementsAtPointJS.FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindTextHighlightJS.FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OriginalViewPortMetaTagContentJS.ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnScrollChangedJS.ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + if let settings = settings { + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests, + allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly) + if settings.useShouldInterceptAjaxRequest { + if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { + configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + } + configuration.userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, + forMainFrameOnly: pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: settings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: settings.useOnAjaxProgress)) + } + if settings.useShouldInterceptFetchRequest { + configuration.userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if settings.useOnLoadResource { + configuration.userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if !settings.supportZoom { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + } else if settings.enableViewportScale { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) } - configuration.userContentController.addPluginScript(INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useShouldInterceptFetchRequest { - configuration.userContentController.addPluginScript(INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useOnLoadResource { - configuration.userContentController.addPluginScript(ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) - } - if !settings.supportZoom { - configuration.userContentController.addPluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - } else if settings.enableViewportScale { - configuration.userContentController.addPluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) } } - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") - configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived") configuration.userContentController.addUserOnlyScripts(initialUserScripts) configuration.userContentController.sync(scriptMessageHandler: self) } public static func preWKWebViewConfiguration(settings: InAppWebViewSettings?) -> WKWebViewConfiguration { let configuration = WKWebViewConfiguration() - + // initialzie WKUserContentController here to fix possible "undefined is not an object (evaluating 'window.webkit.messageHandlers')" javascript error + configuration.userContentController = WKUserContentController() configuration.processPool = WKProcessPoolManager.sharedProcessPool if let settings = settings { @@ -354,11 +374,11 @@ public class InAppWebView: WKWebView, WKUIDelegate, if #available(macOS 11.0, *) { let contentWorlds = configuration.userContentController.getContentWorlds(with: windowId) for contentWorld in contentWorlds { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source, contentWorld: contentWorld) } } else { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source) } } @@ -547,6 +567,10 @@ public class InAppWebView: WKWebView, WKUIDelegate, } } + if newSettingsMap["alpha"] != nil, settings?.alpha != newSettings.alpha, let viewAlpha = newSettings.alpha { + alphaValue = CGFloat(viewAlpha) + } + if (newSettingsMap["incognito"] != nil && settings?.incognito != newSettings.incognito && newSettings.incognito) { configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent() } else if (newSettingsMap["cacheEnabled"] != nil && settings?.cacheEnabled != newSettings.cacheEnabled && newSettings.cacheEnabled) { @@ -566,33 +590,40 @@ public class InAppWebView: WKWebView, WKUIDelegate, if newSettingsMap["enableViewportScale"] != nil && settings?.enableViewportScale != newSettings.enableViewportScale { if !newSettings.enableViewportScale { - if configuration.userContentController.userScripts.contains(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) - evaluateJavaScript(NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(EnableViewportScaleJS.NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE()) } } else { - evaluateJavaScript(ENABLE_VIEWPORT_SCALE_JS_SOURCE) - configuration.userContentController.addUserScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) + evaluateJavaScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["supportZoom"] != nil && settings?.supportZoom != newSettings.supportZoom { if newSettings.supportZoom { - if configuration.userContentController.userScripts.contains(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - evaluateJavaScript(SUPPORT_ZOOM_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(SupportZoomJS.SUPPORT_ZOOM_JS_SOURCE()) } } else { - evaluateJavaScript(NOT_SUPPORT_ZOOM_JS_SOURCE) - configuration.userContentController.addUserScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) + evaluateJavaScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["useOnLoadResource"] != nil && settings?.useOnLoadResource != newSettings.useOnLoadResource { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE, - enable: newSettings.useOnLoadResource, - pluginScript: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: OnLoadResourceJS.FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE(), + enable: newSettings.useOnLoadResource, + pluginScript: OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useOnLoadResource = false } @@ -600,28 +631,58 @@ public class InAppWebView: WKWebView, WKUIDelegate, if newSettingsMap["useShouldInterceptAjaxRequest"] != nil && settings?.useShouldInterceptAjaxRequest != newSettings.useShouldInterceptAjaxRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptAjaxRequest, - pluginScript: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptAjaxRequest, + pluginScript: InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: newSettings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: newSettings.useOnAjaxProgress)) + } } else { newSettings.useShouldInterceptAjaxRequest = false } } + if newSettingsMap["useOnAjaxReadyStateChange"] != nil && settings?.useOnAjaxReadyStateChange != newSettings.useOnAjaxReadyStateChange { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(newSettings.useOnAjaxReadyStateChange);") + } + } else { + newSettings.useOnAjaxReadyStateChange = false + } + } + + if newSettingsMap["useOnAjaxProgress"] != nil && settings?.useOnAjaxProgress != newSettings.useOnAjaxProgress { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(newSettings.useOnAjaxProgress);") + } + } else { + newSettings.useOnAjaxProgress = false + } + } + if newSettingsMap["interceptOnlyAsyncAjaxRequests"] != nil && settings?.interceptOnlyAsyncAjaxRequests != newSettings.interceptOnlyAsyncAjaxRequests { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled, let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE, - enable: newSettings.interceptOnlyAsyncAjaxRequests, - pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE(), + enable: newSettings.interceptOnlyAsyncAjaxRequests, + pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + } } } if newSettingsMap["useShouldInterceptFetchRequest"] != nil && settings?.useShouldInterceptFetchRequest != newSettings.useShouldInterceptFetchRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptFetchRequest, - pluginScript: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptFetchRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptFetchRequest, + pluginScript: InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useShouldInterceptFetchRequest = false } @@ -867,6 +928,17 @@ public class InAppWebView: WKWebView, WKUIDelegate, } } +#if compiler(>=6.0) + public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? = nil) { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { + if let completionHandler = completionHandler { + completionHandler(nil, nil) + } + return + } + super.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) + } +#else public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) { if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { if let completionHandler = completionHandler { @@ -876,6 +948,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, } super.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) } +#endif @available(macOS 11.0, *) public func evaluateJavaScript(_ javaScript: String, frame: WKFrameInfo? = nil, contentWorld: WKContentWorld, completionHandler: ((Result) -> Void)? = nil) { @@ -956,7 +1029,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, let functionArgumentNames = functionArgumentNamesList.joined(separator: ", ") let functionArgumentValues = functionArgumentValuesList.joined(separator: ", ") - jsToInject = CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS + jsToInject = CallAsyncJavaScriptBelowIOS14WrapperJS.CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, with: functionArgumentNames) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, with: functionArgumentValues) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, with: Util.JSONStringify(value: arguments)) @@ -987,15 +1060,15 @@ public class InAppWebView: WKWebView, WKUIDelegate, scriptAttributes += " script.id = '\(scriptIdEscaped)'; " scriptAttributes += """ script.onload = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); } }; """ scriptAttributes += """ script.onerror = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); } }; """ @@ -1156,7 +1229,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil // cancel the download @@ -1174,7 +1247,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: response.suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil } @@ -1267,7 +1340,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: navigationResponse.response.expectedContentLength, suggestedFilename: navigationResponse.response.suggestedFilename, textEncodingName: navigationResponse.response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) if useOnNavigationResponse == nil || !useOnNavigationResponse! { decisionHandler(.cancel) } @@ -1301,7 +1374,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, initializeWindowIdJS() InAppWebView.credentialsProposed = [] - evaluateJavaScript(PLATFORM_READY_JS_SOURCE, completionHandler: nil) + evaluateJavaScript(JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE, completionHandler: nil) channelDelegate?.onLoadStop(url: url?.absoluteString) @@ -1443,7 +1516,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, if let scheme = challenge.protectionSpace.protocol, scheme == "https" { // workaround for ProtectionSpace SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { if let sslCertificate = challenge.protectionSpace.sslCertificate { DispatchQueue.main.async { InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate @@ -1463,7 +1536,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, break case 1: // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let exceptions = SecTrustCopyExceptions(serverTrust) SecTrustSetExceptions(serverTrust, exceptions) let credential = URLCredential(trust: serverTrust) @@ -2086,167 +2159,251 @@ public class InAppWebView: WKWebView, WKUIDelegate, // } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard javaScriptBridgeEnabled else { + return + } + guard let body = message.body as? [String: Any?] else { return } - if ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"].contains(message.name) { - var messageLevel = 1 - switch (message.name) { - case "consoleLog": - messageLevel = 1 - break; - case "consoleDebug": - // on Android, console.debug is TIP - messageLevel = 0 - break; - case "consoleError": - messageLevel = 3 - break; - case "consoleInfo": - // on Android, console.info is LOG - messageLevel = 1 - break; - case "consoleWarn": - messageLevel = 2 - break; - default: - messageLevel = 1 - break; + guard let bridgeSecret = body["_bridgeSecret"] as? String, bridgeSecret == exceptedBridgeSecret else { + print("Bridge access attempt with wrong secret token, possibly from malicious code from origin \(message.frameInfo.securityOrigin)") + return + } + + var sourceOrigin: URL? = nil + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + let requestUrl = message.frameInfo.request.url + + var isOriginAllowed = false + if let javaScriptHandlersOriginAllowList = settings?.javaScriptHandlersOriginAllowList { + if let origin = sourceOrigin?.absoluteString { + for allowedOrigin in javaScriptHandlersOriginAllowList { + if origin.range(of: allowedOrigin, options: .regularExpression, range: nil, locale: nil) != nil { + isOriginAllowed = true + break + } + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true + } + + if !isOriginAllowed { + print("Bridge access attempt from an origin not allowed: \(message.frameInfo.securityOrigin)") + return + } + + if message.name == "callHandler" { + guard let handlerName = body["handlerName"] as? String else { + print("handlerName is null or undefined") + return } - let consoleMessage = body["message"] as? String ?? "" let _windowId = body["_windowId"] as? Int64 var webView = self if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } - webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) - } else if message.name == "callHandler", let handlerName = body["handlerName"] as? String { - if handlerName == "onPrintRequest" { - let settings = PrintJobSettings() - settings.handledByClient = true - if let printJobId = printCurrentPage(settings: settings) { - let callback = WebViewChannelDelegate.PrintRequestCallback() - callback.nonNullSuccess = { (handledByClient: Bool) in - return !handledByClient + var isInternalHandler = true + switch (handlerName) { + case "onPrintRequest": + let settings = PrintJobSettings() + settings.handledByClient = true + if let printJobId = webView.printCurrentPage(settings: settings) { + let callback = WebViewChannelDelegate.PrintRequestCallback() + callback.nonNullSuccess = { (handledByClient: Bool) in + return !handledByClient + } + callback.defaultBehaviour = { (handledByClient: Bool?) in + if let printJob = webView.plugin?.printJobManager?.jobs[printJobId] { + printJob?.disposeWhenDidRun = true + } + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + webView.channelDelegate?.onPrintRequest(url: webView.url, printJobId: printJobId, callback: callback) + } + break + case "onConsoleMessage": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first { + var messageLevel = 1 + switch (jsonData["level"] as? String) { + case "log": + messageLevel = 1 + break + case "debug": + // on Android, console.debug is TIP + messageLevel = 0 + break + case "error": + messageLevel = 3 + break + case "info": + // on Android, console.info is LOG + messageLevel = 1 + break + case "warn": + messageLevel = 2 + break + default: + messageLevel = 1 + break + } + let consoleMessage = jsonData["message"] as? String ?? "" + + webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) + } } - callback.defaultBehaviour = { [weak self] (handledByClient: Bool?) in - if let printJob = self?.plugin?.printJobManager?.jobs[printJobId] { - printJob?.disposeNoDismiss() + break + case "onFindResultReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let findResult = jsonData["findResult"] as? [String: Any], + let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, + let numberOfMatches = findResult["numberOfMatches"] as? Int, + let isDoneCounting = findResult["isDoneCounting"] as? Bool { + webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) } } - callback.error = { [weak callback] (code: String, message: String?, details: Any?) in - print(code + ", " + (message ?? "")) - callback?.defaultBehaviour(nil) + break + case "onCallAsyncJavaScriptResultBelowIOS14Received": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let resultUuid = jsonData["resultUuid"] as? String, + let result = webView.callAsyncJavaScriptBelowMacOS11Results[resultUuid] { + result([ + "value": jsonData["value"], + "error": jsonData["error"] + ]) + webView.callAsyncJavaScriptBelowMacOS11Results.removeValue(forKey: resultUuid) + } } - channelDelegate?.onPrintRequest(url: url, printJobId: printJobId, callback: callback) - } - return + break + case "onWebMessagePortMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let webMessageChannelId = jsonData["webMessageChannelId"] as? String, + let index = jsonData["index"] as? Int64 { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { + webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) + } + } + } + break + case "onWebMessageListenerPostMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, let jsObjectName = jsonData["jsObjectName"] as? String { + var webMessage: WebMessage? = nil + if let webMessageMap = body["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageListener = webView.webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { + let isMainFrame = message.frameInfo.isMainFrame + + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + + if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { + return + } + + var sourceOrigin: URL? = nil + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + } + } + } + break + case "onScrollChanged": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let x = jsonData["x"] as? Int, + let y = jsonData["y"] as? Int { + webView.channelDelegate?.onScrollChanged(x: x, y: y) + } + } + break + default: + isInternalHandler = false + break } let _callHandlerID = body["_callHandlerID"] as? Int64 ?? 0 - let args = body["args"] as? String ?? "" - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView + if isInternalHandler { + evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; +} +""", completionHandler: nil) + return } + let args = body["args"] as? String ?? "" + let callback = WebViewChannelDelegate.CallJsHandlerCallback() - callback.defaultBehaviour = { [weak self] (response: Any?) in + callback.defaultBehaviour = { (response: Any?) in var json = "null" if let r = response as? String { json = r } - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].resolve(\(json)); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(\(json)); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } - callback.error = { [weak self] (code: String, message: String?, details: Any?) in + callback.error = { (code: String, message: String?, details: Any?) in let errorMessage = code + (message != nil ? ", " + (message ?? "") : "") print(errorMessage) - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } if let channelDelegate = webView.channelDelegate { - channelDelegate.onCallJsHandler(handlerName: handlerName, args: args, callback: callback) - } - } else if message.name == "onFindResultReceived", - let findResult = body["findResult"] as? [String: Any], - let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, - let numberOfMatches = findResult["numberOfMatches"] as? Int, - let isDoneCounting = findResult["isDoneCounting"] as? Bool { - - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - } else if message.name == "onScrollChanged", - let x = body["x"] as? Int, - let y = body["y"] as? Int { - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.channelDelegate?.onScrollChanged(x: x, y: y) - } else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received", - let resultUuid = body["resultUuid"] as? String, - let result = callAsyncJavaScriptBelowMacOS11Results[resultUuid] { - result([ - "value": body["value"], - "error": body["error"] - ]) - callAsyncJavaScriptBelowMacOS11Results.removeValue(forKey: resultUuid) - } else if message.name == "onWebMessagePortMessageReceived", - let webMessageChannelId = body["webMessageChannelId"] as? String, - let index = body["index"] as? Int64 { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageChannel = webMessageChannels[webMessageChannelId] { - webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) - } - } else if message.name == "onWebMessageListenerPostMessageReceived", let jsObjectName = body["jsObjectName"] as? String { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { - let isMainFrame = message.frameInfo.isMainFrame - - let securityOrigin = message.frameInfo.securityOrigin - let scheme = securityOrigin.protocol - let host = securityOrigin.host - let port = securityOrigin.port - - if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { - return - } - - var sourceOrigin: URL? = nil - if !scheme.isEmpty, !host.isEmpty { - sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") - } - webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + let data = JavaScriptHandlerFunctionData( + args: args, isMainFrame: message.frameInfo.isMainFrame, + origin: sourceOrigin?.absoluteString ?? "", + requestUrl: requestUrl?.absoluteString ?? "" + ) + channelDelegate.onCallJsHandler(handlerName: handlerName, data: data, callback: callback) } } } @@ -2466,6 +2623,48 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } + public func clearFocus() -> Bool { + return (self.superview?.window ?? self.window)?.makeFirstResponder(nil) ?? false + } + + public func requestFocus() -> Bool { + return (self.superview?.window ?? self.window)?.makeFirstResponder(self) ?? false + } + + // Workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380 + // TODO: remove when Flutter fixes this + private var _isFirstResponder = true + override open func becomeFirstResponder() -> Bool { + _isFirstResponder = true + return super.becomeFirstResponder() + } + private func _fixFocus(callback: @escaping () -> Void) { + if _isFirstResponder, let channelDelegate = channelDelegate { + _isFirstResponder = false + channelDelegate._onMouseDown(callback: { [weak self] in + let _ = self?.requestFocus() + callback() + }) + } else { + callback() + } + } + override public func mouseDown(with event: NSEvent) { + _fixFocus { + super.mouseDown(with: event) + } + } + override public func rightMouseDown(with event: NSEvent) { + _fixFocus { + super.rightMouseDown(with: event) + } + } + override public func otherMouseDown(with event: NSEvent) { + _fixFocus { + super.otherMouseDown(with: event) + } + } + public func getCertificate() -> SslCertificate? { guard let scheme = url?.scheme, scheme == "https", @@ -2531,7 +2730,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } @@ -2582,6 +2781,16 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } + @available(macOS 12.0, *) + public func saveState() -> Data? { + return interactionState is NSData || interactionState is Data ? interactionState as? Data : nil + } + + @available(macOS 12.0, *) + public func restoreState(state: Data) { + interactionState = state + } + public func runWindowBeforeCreatedCallbacks() { let callbacks = windowBeforeCreatedCallbacks callbacks.forEach { (callback) in @@ -2619,9 +2828,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { interceptOnlyAsyncAjaxRequestsPluginScript = nil if windowId == nil { configuration.userContentController.removeAllPluginScriptMessageHandlers() - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") configuration.userContentController.removeAllUserScripts() if #available(macOS 10.13, *) { configuration.userContentController.removeAllContentRuleLists() diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift index 1afc4fe8d..d4d369bd6 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift @@ -20,7 +20,7 @@ public class InAppWebViewManager: ChannelDelegate { var windowAutoincrementId: Int64 = 0 init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } @@ -51,6 +51,14 @@ public class InAppWebViewManager: ChannelDelegate { clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: { result(true) }) + case "setJavaScriptBridgeName": + let bridgeName = arguments!["bridgeName"] as! String + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME(bridgeName: bridgeName) + result(true) + break + case "getJavaScriptBridgeName": + result(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) + break default: result(FlutterMethodNotImplemented) break diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift index 7b2b9cba5..d2785dfca 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift @@ -25,6 +25,8 @@ public class InAppWebViewSettings: ISettings { var contentBlockers: [[String: [String : Any]]] = [] var minimumFontSize = 0 var useShouldInterceptAjaxRequest = false + var useOnAjaxReadyStateChange = false + var useOnAjaxProgress = false var interceptOnlyAsyncAjaxRequests = true var useShouldInterceptFetchRequest = false var incognito = false @@ -55,12 +57,26 @@ public class InAppWebViewSettings: ISettings { var isElementFullscreenEnabled = true var isInspectable = false var shouldPrintBackgrounds = false + var javaScriptHandlersOriginAllowList: [String]? = nil + var javaScriptBridgeEnabled = true + var javaScriptBridgeOriginAllowList: [String]? = nil + var javaScriptBridgeForMainFrameOnly = false + var pluginScriptsOriginAllowList: [String]? = nil + var pluginScriptsForMainFrameOnly = false + var alpha: Double? = nil override init(){ super.init() } override func parse(settings: [String: Any?]) -> InAppWebViewSettings { + var settings = settings // re-assing to be able to use removeValue + // nullable values with primitive type (Int, Double, etc.) + // must be handled here as super.parse will not work + if let alphaValue = settings["alpha"] as? Double { + alpha = alphaValue + settings.removeValue(forKey: "alpha") + } let _ = super.parse(settings: settings) if #available(macOS 10.15, *) {} else { applePayAPIEnabled = false diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index c806e0dec..76deada77 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -20,11 +20,9 @@ public class WebMessageChannel: FlutterMethodCallDelegate { self.id = id self.plugin = plugin super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.ports = [ WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self), WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self) @@ -36,7 +34,7 @@ public class WebMessageChannel: FlutterMethodCallDelegate { if let webView = self.webView { webView.evaluateJavascript(source: """ (function() { - \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"] = new MessageChannel(); })(); """) { (_) in completionHandler?(self) @@ -61,11 +59,11 @@ public class WebMessageChannel: FlutterMethodCallDelegate { ports.removeAll() webView?.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; if (webMessageChannel != null) { webMessageChannel.port1.close(); webMessageChannel.port2.close(); - delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + delete \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; } })(); """) diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift index 270f01d3d..782861b82 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -24,11 +24,9 @@ public class WebMessageListener: FlutterMethodCallDelegate { self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) } public func assertOriginRulesValid() throws { @@ -97,23 +95,30 @@ public class WebMessageListener: FlutterMethodCallDelegate { }.joined(separator: ", ") let source = """ (function() { + \(WebMessageListener.isOriginAllowedJs) + var allowedOriginRules = [\(allowedOriginRulesString)]; var isPageBlank = window.location.href === "about:blank"; var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null; var host = !isPageBlank ? window.location.hostname : null; var port = !isPageBlank ? window.location.port : null; - if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) { + if (_isOriginAllowed(allowedOriginRules, scheme, host, port)) { window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)'); } })(); """ + + let allowedOriginRules = webView.settings?.pluginScriptsOriginAllowList + let forMainFrameOnly = webView.settings?.pluginScriptsForMainFrameOnly ?? true + webView.configuration.userContentController.addPluginScript(PluginScript( groupName: "WebMessageListener-" + id + "-" + jsObjectName, source: source, injectionTime: .atDocumentStart, - forMainFrameOnly: false, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, requiredInAllContentWorlds: false, - messageHandlerNames: ["onWebMessageListenerPostMessageReceived"] + messageHandlerNames: [] )) webView.configuration.userContentController.sync(scriptMessageHandler: webView) } @@ -178,6 +183,87 @@ public class WebMessageListener: FlutterMethodCallDelegate { } return false } + + private static let isOriginAllowedJs = """ + var _normalizeIPv6 = function(ip_string) { + // replace ipv4 address if any + var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); + if (ipv4) { + ip_string = ipv4[1]; + ipv4 = ipv4[2].match(/[0-9]+/g); + for (var i = 0;i < 4;i ++) { + var byte = parseInt(ipv4[i],10); + ipv4[i] = ("0" + byte.toString(16)).substr(-2); + } + ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; + } + + // take care of leading and trailing :: + ip_string = ip_string.replace(/^:|:$/g, ''); + + var ipv6 = ip_string.split(':'); + + for (var i = 0; i < ipv6.length; i ++) { + var hex = ipv6[i]; + if (hex != "") { + // normalize leading zeros + ipv6[i] = ("0000" + hex).substr(-4); + } + else { + // normalize grouped zeros :: + hex = []; + for (var j = ipv6.length; j <= 8; j ++) { + hex.push('0000'); + } + ipv6[i] = hex.join(':'); + } + } + + return ipv6.join(':'); + }; + + var _isOriginAllowed = function(allowedOriginRules, scheme, host, port) { + for (var rule of allowedOriginRules) { + if (rule === "*") { + return true; + } + if (scheme == null || scheme === "") { + continue; + } + if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { + continue; + } + var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; + var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; + var IPv6 = null; + if (rule.host != null && rule.host[0] === "[") { + try { + IPv6 = _normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); + } catch {} + } + var hostIPv6 = null; + try { + hostIPv6 = _normalizeIPv6(host); + } catch {} + + var schemeAllowed = scheme == rule.scheme; + + var hostAllowed = rule.host == null || + rule.host === "" || + host === rule.host || + (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || + (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); + + var portAllowed = rulePort === currentPort; + + if (schemeAllowed && hostAllowed && portAllowed) { + return true; + } + } + return false; + }; + """ + public func dispose() { channelDelegate?.dispose() diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift index 9eb906e57..27389babb 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -370,6 +370,12 @@ public class WebViewChannelDelegate: ChannelDelegate { result(nil) } break + case .clearFocus: + result(webView?.clearFocus()) + break + case .requestFocus: + result(webView?.requestFocus()) + break case .getCertificate: result(webView?.getCertificate()?.toMap()) break @@ -658,6 +664,23 @@ public class WebViewChannelDelegate: ChannelDelegate { } else { result(false) } + break + case .saveState: + if let webView = webView, #available(macOS 12.0, *) { + result(webView.saveState()) + } else { + result(nil) + } + break + case .restoreState: + if let webView = webView, #available(macOS 12.0, *) { + let state = arguments!["state"] as! FlutterStandardTypedData + webView.restoreState(state: state.data) + result(true) + } else { + result(false) + } + break } } @@ -680,8 +703,8 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onScrollChanged", arguments: arguments) } - public func onDownloadStartRequest(request: DownloadStartRequest) { - channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) + public func onDownloadStarting(request: DownloadStartRequest) { + channel?.invokeMethod("onDownloadStarting", arguments: request.toMap()) } public func onCreateContextMenu(hitTestResult: HitTestResult) { @@ -928,7 +951,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -960,7 +983,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -992,7 +1015,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1046,14 +1069,14 @@ public class WebViewChannelDelegate: ChannelDelegate { } } - public func onCallJsHandler(handlerName: String, args: String, callback: CallJsHandlerCallback) { + public func onCallJsHandler(handlerName: String, data: JavaScriptHandlerFunctionData, callback: CallJsHandlerCallback) { if channel == nil { callback.defaultBehaviour(nil) return } let arguments: [String: Any?] = [ "handlerName": handlerName, - "args": args + "data": data.toMap() ] channel?.invokeMethod("onCallJsHandler", arguments: arguments, callback: callback) } @@ -1105,7 +1128,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1166,6 +1189,16 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onPrintRequest", arguments: arguments, callback: callback) } + internal func _onMouseDown(callback: @escaping () -> Void) { + if channel == nil { + return + } + let arguments: [String:Any] = [:]; + channel?.invokeMethod("_onMouseDown", arguments: arguments) {(result) -> Void in + callback() + } + } + public override func dispose() { super.dispose() webView = nil diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift index 4810bae3b..b432dc608 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift @@ -58,6 +58,8 @@ public enum WebViewChannelDelegateMethods: String { case getSelectedText = "getSelectedText" case getScrollX = "getScrollX" case getScrollY = "getScrollY" + case clearFocus = "clearFocus" + case requestFocus = "requestFocus" case getCertificate = "getCertificate" case addUserScript = "addUserScript" case removeUserScript = "removeUserScript" @@ -84,4 +86,6 @@ public enum WebViewChannelDelegateMethods: String { case getMicrophoneCaptureState = "getMicrophoneCaptureState" case setMicrophoneCaptureState = "setMicrophoneCaptureState" case loadSimulatedRequest = "loadSimulatedRequest" + case saveState = "saveState" + case restoreState = "restoreState" } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift index 530efdd79..57f6dddaa 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift @@ -25,7 +25,7 @@ import SafariServices public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { - var registrar: FlutterPluginRegistrar? + var registrar: FlutterPluginRegistrar var platformUtil: PlatformUtil? var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? @@ -35,13 +35,14 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { var headlessInAppWebViewManager: HeadlessInAppWebViewManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? var printJobManager: PrintJobManager? + var proxyManager: Any? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] public init(with registrar: FlutterPluginRegistrar) { - super.init() self.registrar = registrar + super.init() registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) @@ -55,6 +56,9 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { myWebStorageManager = MyWebStorageManager(plugin: self) webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) printJobManager = PrintJobManager(plugin: self) + if #available(macOS 14.0, *) { + proxyManager = ProxyManager(plugin: self) + } } public static func register(with registrar: FlutterPluginRegistrar) { @@ -73,7 +77,7 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { credentialDatabase?.dispose() credentialDatabase = nil if #available(macOS 10.13, *) { - (myCookieManager as! MyCookieManager?)?.dispose() + (myCookieManager as? MyCookieManager)?.dispose() myCookieManager = nil } myWebStorageManager?.dispose() @@ -82,5 +86,9 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { webAuthenticationSessionManager = nil printJobManager?.dispose() printJobManager = nil + if #available(macOS 14.0, *) { + (proxyManager as? ProxyManager)?.dispose() + proxyManager = nil + } } } diff --git a/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift b/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift index 86e62d7ef..856ac1a25 100755 --- a/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift @@ -16,7 +16,7 @@ public class MyCookieManager: ChannelDelegate { static var httpCookieStore = WKWebsiteDataStore.default().httpCookieStore init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift b/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift index a20b46e6a..c77840bbb 100755 --- a/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift @@ -15,7 +15,7 @@ public class MyWebStorageManager: ChannelDelegate { static var websiteDataStore = WKWebsiteDataStore.default() init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift b/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift index c9859d7d8..20f96e9cf 100644 --- a/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift +++ b/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift @@ -13,7 +13,7 @@ public class PlatformUtil: ChannelDelegate { var plugin: InAppWebViewFlutterPlugin? init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } @@ -51,6 +51,7 @@ public class PlatformUtil: ChannelDelegate { static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String { let formatter = DateFormatter() + formatter.locale = locale formatter.dateFormat = format formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift index 5622691a1..f1f83db00 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift @@ -7,15 +7,28 @@ import Foundation -let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """ -(function(obj) { - (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { - \(PluginScriptsUtil.VAR_FUNCTION_BODY) - })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }).catch(function(error) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }); - return null; -})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); -""" +public class CallAsyncJavaScriptBelowIOS14WrapperJS { + + public static func CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() -> String { + return """ + (function(obj) { + (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { + \(PluginScriptsUtil.VAR_FUNCTION_BODY) + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': value, + 'error': null, + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }).catch(function(error) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': null, + 'error': error + '', + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }); + return null; + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift index d244d8993..5636ea62c 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift @@ -7,46 +7,59 @@ import Foundation -let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" - -let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript( - groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: CONSOLE_LOG_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"]) - -// the message needs to be concatenated with '' in order to have the same behavior like on Android -let CONSOLE_LOG_JS_SOURCE = """ -(function(console) { - - function _callHandler(oldLog, args) { - var message = ''; - for (var i in args) { - try { - message += message === '' ? args[i] : ' ' + args[i]; - } catch(ignored) {} - } - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId}); +public class ConsoleLogJS { + + public static let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame. + // Using it also on non-main frames could cause issues + // such as https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738 + public static func CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: CONSOLE_LOG_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) } - - var oldLogs = { - 'consoleLog': console.log, - 'consoleDebug': console.debug, - 'consoleError': console.error, - 'consoleInfo': console.info, - 'consoleWarn': console.warn - }; - - for (var k in oldLogs) { - (function(oldLog) { - console[oldLog.replace('console', '').toLowerCase()] = function() { - oldLogs[oldLog].apply(null, arguments); - _callHandler(oldLog, arguments); + + // the message needs to be concatenated with '' in order to have the same behavior like on Android + public static func CONSOLE_LOG_JS_SOURCE() -> String { + return """ + (function(console) { + + function _callHandler(logLevel, args) { + var message = ''; + for (var i in args) { + try { + message += message === '' ? args[i] : ' ' + args[i]; + } catch(_) {} + } + try { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).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); } - })(k); + })(window.console); + """ } -})(window.console); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift index 593ba00eb..efa07583a 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift @@ -7,30 +7,39 @@ import Foundation -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" - -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', 'width=device-width'); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" - -let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" +public class EnableViewportScaleJS { + + public static let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift index b3c22282e..26220f8a5 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift @@ -7,67 +7,75 @@ import Foundation -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" - -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_ELEMENTS_AT_POINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -/** - https://developer.android.com/reference/android/webkit/WebView.HitTestResult - */ -let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) { - var 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 - }; - var element = document.elementFromPoint(x, y); - var data = { - type: 0, - extra: null - }; - while (element) { - if (element.tagName === 'IMG' && element.src) { - if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { - data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; - } else { - data.type = hitTestResultType.IMAGE_TYPE; - } - data.extra = element.src; - break; - } else if (element.tagName === 'A' && element.href) { - if (element.href.indexOf('mailto:') === 0) { - data.type = hitTestResultType.EMAIL_TYPE; - data.extra = element.href.replace('mailto:', ''); - } else if (element.href.indexOf('tel:') === 0) { - data.type = hitTestResultType.PHONE_TYPE; - data.extra = element.href.replace('tel:', ''); - } else if (element.href.indexOf('geo:') === 0) { - data.type = hitTestResultType.GEO_TYPE; - data.extra = element.href.replace('geo:', ''); - } else { - data.type = hitTestResultType.SRC_ANCHOR_TYPE; - data.extra = element.href; +public class FindElementsAtPointJS { + public static let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_ELEMENTS_AT_POINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + /** + https://developer.android.com/reference/android/webkit/WebView.HitTestResult + */ + public static func FIND_ELEMENTS_AT_POINT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint = function(x, y) { + var 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 + }; + var element = document.elementFromPoint(x, y); + var data = { + type: 0, + extra: null + }; + while (element) { + if (element.tagName === 'IMG' && element.src) { + if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { + data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; + } else { + data.type = hitTestResultType.IMAGE_TYPE; + } + data.extra = element.src; + break; + } else if (element.tagName === 'A' && element.href) { + if (element.href.indexOf('mailto:') === 0) { + data.type = hitTestResultType.EMAIL_TYPE; + data.extra = element.href.replace('mailto:', ''); + } else if (element.href.indexOf('tel:') === 0) { + data.type = hitTestResultType.PHONE_TYPE; + data.extra = element.href.replace('tel:', ''); + } else if (element.href.indexOf('geo:') === 0) { + data.type = hitTestResultType.GEO_TYPE; + data.extra = element.href.replace('geo:', ''); + } else { + data.type = hitTestResultType.SRC_ANCHOR_TYPE; + data.extra = element.href; + } + break; + } else if ( + (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || + element.tagName === 'TEXTAREA') { + data.type = hitTestResultType.EDIT_TEXT_TYPE + } + element = element.parentNode; } - break; - } else if ( - (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || - element.tagName === 'TEXTAREA') { - data.type = hitTestResultType.EDIT_TEXT_TYPE + return data; } - element = element.parentNode; + """ } - return data; } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift index f0db61452..5006f04b7 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift @@ -7,181 +7,180 @@ import Foundation -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" -let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount" -let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight" -let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting" - -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_TEXT_HIGHLIGHT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onFindResultReceived"]) - -let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """ -\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) { - if (element) { - if (element.nodeType == 3) { - // Text node - - var elementTmp = element; - while (true) { - var value = elementTmp.nodeValue; // Search for keyword in text node - var idx = value.toLowerCase().indexOf(keyword); - - if (idx < 0) break; - - var span = document.createElement("span"); - var text = document.createTextNode(value.substr(idx, keyword.length)); - span.appendChild(text); - - span.setAttribute( - "id", - "\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ); - span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00"; - span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); - - text = document.createTextNode(value.substr(idx + keyword.length)); - element.deleteData(idx, value.length - idx); - - var next = element.nextSibling; - element.parentNode.insertBefore(span, next); - element.parentNode.insertBefore(text, next); - element = text; - - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++; - elementTmp = document.createTextNode( - value.substr(idx + keyword.length) - ); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId +public class FindTextHighlightJS { + public static let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" + public static func FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._searchResultCount" + } + public static func FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._currentHighlight" + } + public static func FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._isDoneCounting" + } + + // This plugin is only for main frame + public static func FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_TEXT_HIGHLIGHT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func FIND_TEXT_HIGHLIGHT_JS_SOURCE() -> String { + return """ + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement = function(element, keyword) { + if (element) { + if (element.nodeType == 3) { + // Text node + + var elementTmp = element; + while (true) { + var value = elementTmp.nodeValue; // Search for keyword in text node + var idx = value.toLowerCase().indexOf(keyword); + + if (idx < 0) break; + + var span = document.createElement("span"); + var text = document.createTextNode(value.substr(idx, keyword.length)); + span.appendChild(text); + + span.setAttribute( + "id", + "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ); + span.setAttribute("class", "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) == 0 ? "#FF9732" : "#FFFF00"; + span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); + + text = document.createTextNode(value.substr(idx + keyword.length)); + element.deleteData(idx, value.length - idx); + + var next = element.nextSibling; + element.parentNode.insertBefore(span, next); + element.parentNode.insertBefore(text, next); + element = text; + + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE())++; + elementTmp = document.createTextNode( + value.substr(idx + keyword.length) + ); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + } else if (element.nodeType == 1) { + // Element node + if ( + element.style.display != "none" && + element.nodeName.toLowerCase() != "select" + ) { + for (var i = element.childNodes.length - 1; i >= 0; i--) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement( + element.childNodes[element.childNodes.length - 1 - i], + keyword + ); + } + } } - ); - } - } else if (element.nodeType == 1) { - // Element node - if ( - element.style.display != "none" && - element.nodeName.toLowerCase() != "select" - ) { - for (var i = element.childNodes.length - 1; i >= 0; i--) { - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement( - element.childNodes[element.childNodes.length - 1 - i], - keyword - ); + } } - } - } - } -} - -// the main entry point to start the search -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) { - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches(); - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase()); - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true; - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId - } - ); -} - -// helper function, recursively removes the highlights in elements and their children -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) { - if (element) { - if (element.nodeType == 1) { - if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") { - var text = element.removeChild(element.firstChild); - element.parentNode.insertBefore(text, element); - element.parentNode.removeChild(element); - return true; - } else { - var normalize = false; - for (var i = element.childNodes.length - 1; i >= 0; i--) { - if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) { - normalize = true; + + // the main entry point to start the search + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync = function(keyword) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches(); + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement(document.body, keyword.toLowerCase()); + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = true; + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + + // helper function, recursively removes the highlights in elements and their children + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement = function(element) { + if (element) { + if (element.nodeType == 1) { + if (element.getAttribute("class") == "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight") { + var text = element.removeChild(element.firstChild); + element.parentNode.insertBefore(text, element); + element.parentNode.removeChild(element); + return true; + } else { + var normalize = false; + for (var i = element.childNodes.length - 1; i >= 0; i--) { + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(element.childNodes[i])) { + normalize = true; + } + } + if (normalize) { + element.normalize(); + } + } + } } + return false; } - if (normalize) { - element.normalize(); + + // the main entry point to remove the highlights + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches = function() { + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(document.body); } - } - } - } - return false; -} - -// the main entry point to remove the highlights -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() { - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) { - if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return; - - var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1); - idx = - idx < 0 - ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1 - : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ? 0 - : idx; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx; - - var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx); - if (scrollTo) { - var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - for (var i = 0; i < highlights.length; i++) { - var span = highlights[i]; - span.style.backgroundColor = "#FFFF00"; - } - scrollTo.style.backgroundColor = "#FF9732"; - - scrollTo.scrollIntoView({ - behavior: "auto", - block: "center" - }); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext = function(forward) { + if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) <= 0) return; + + var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) + (forward ? +1 : -1); + idx = + idx < 0 + ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) - 1 + : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ? 0 + : idx; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = idx; + + var scrollTo = document.getElementById("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + idx); + if (scrollTo) { + var highlights = document.getElementsByClassName("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + for (var i = 0; i < highlights.length; i++) { + var span = highlights[i]; + span.style.backgroundColor = "#FFFF00"; + } + scrollTo.style.backgroundColor = "#FF9732"; + + scrollTo.scrollIntoView({ + behavior: "auto", + block: "center" + }); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } } - ); - } + """ + } } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift index 4a68e9b6b..e8ca40c76 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift @@ -7,262 +7,288 @@ import Foundation -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest" - - -let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests"; - -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_AJAX_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript { - return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);", - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: [] - ); -} - -let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true; -(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_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(new Uint8Array(request.response)); - return; - case 'blob': - const reader = new FileReader(); - reader.addEventListener('loadend', function() { - callback(new Uint8Array(reader.result)); - }); - reader.readAsArrayBuffer(blob); - return; - case 'document': - callback(request.response.documentElement.outerHTML); - return; - case 'json': - callback(request.response); - return; - }; +public class InterceptAjaxRequestJS { + + public static let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptAjaxRequest" } - 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) { - var self = this; - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - }); - }); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxReadyStateChange" } - }; - ajax.prototype.send = function(data) { - var self = this; - var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false; - if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) { - if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) { - this._flutter_inappwebview_already_onreadystatechange_wrapped = true; - var onreadystatechange = this.onreadystatechange; - this.onreadystatechange = function() { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - if (onreadystatechange != null) { - onreadystatechange(); - } - }); - }); - } else if (onreadystatechange != null) { - onreadystatechange(); - } - }; - } - 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); - \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) { - var ajaxRequest = { - data: data, - 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.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxProgress" + } + + public static func FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._interceptOnlyAsyncAjaxRequests" + } + + public static func INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool, initialUseOnAjaxReadyStateChange: Bool = false, initialUseOnAjaxProgress: Bool = false) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress: initialUseOnAjaxProgress), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) = \(onlyAsync);", + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: [] + ); + } + + public static func INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: Bool, initialUseOnAjaxProgress: Bool) -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) = true; + \(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(initialUseOnAjaxReadyStateChange); + \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(initialUseOnAjaxProgress); + (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_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(new Uint8Array(request.response)); + return; + case 'blob': + const reader = new FileReader(); + reader.addEventListener('loadend', function() { + callback(new Uint8Array(reader.result)); + }); + reader.readAsArrayBuffer(blob); + return; + case 'document': + callback(request.response.documentElement.outerHTML); + return; + case 'json': + callback(request.response); return; }; - if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).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 (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) { - data = result.data; - } else if (result.data.length > 0) { - data = new Uint8Array(result.data); + } + 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 (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) === false || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) == null || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) === false) { + return; + } + var self = this; + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == 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; + }); } - 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; + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + }); + }); + } + }; + ajax.prototype.send = function(data) { + var self = this; + var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) === false; + if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true)) { + if (\(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) { + this._flutter_inappwebview_already_onreadystatechange_wrapped = true; + var realOnreadystatechange = this.onreadystatechange; + this.onreadystatechange = function() { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }); + }); + } else if (realOnreadystatechange != null) { + realOnreadystatechange(); } - 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); - } + 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); + \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(data).then(function(data) { + var ajaxRequest = { + data: data, + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + if (result.data != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) && result.data.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.data); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) || result.data == null) { + data = result.data; + } else if (result.data.length > 0) { + data = 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, data); + }); + }); + } else { + send.call(this, data); } - send.call(self, data); - }); - }); - } else { - send.call(this, data); + }; + })(window.XMLHttpRequest); + """ } - }; -})(window.XMLHttpRequest); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift index 14539811c..953fa3aa8 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift @@ -7,147 +7,157 @@ import Foundation -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest" - -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_FETCH_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true; -(function(fetch) { - if (fetch == null) { - return; - } - window.fetch = async function(resource, init) { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == 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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers); - } - fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials); - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) { - fetchRequest.body = body; - return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { - if (result != null) { - switch (result.action) { - case 0: - var controller = new AbortController(); +public class InterceptFetchRequestJS { + + public static let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptFetchRequest" + } + + public static func INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) = true; + (function(fetch) { + if (fetch == null) { + return; + } + window.fetch = async function(resource, init) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == 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) { - init.signal = controller.signal; - } else { - init = { - signal: controller.signal - }; - } - controller.abort(); - break; - } - if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).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 }; + 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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertHeadersToJson(fetchRequest.headers); + } + fetchRequest.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertCredentialsToJson(fetchRequest.credentials); + return \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(fetchRequest.body).then(function(body) { + fetchRequest.body = body; + return window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { + if (result != null) { + switch (result.action) { + case 0: + var controller = new AbortController(); + if (init != null) { + init.signal = controller.signal; + } else { + init = { + signal: controller.signal + }; + } + controller.abort(); + break; + } + if (result.body != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) && result.body.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.body); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToHeaders(result.headers); + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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 = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).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); } - 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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).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 = \(JAVASCRIPT_UTIL_VAR_NAME).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); + """ } - }; -})(window.fetch); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift index 1549c28be..0e42a3734 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift @@ -7,237 +7,287 @@ import Foundation -let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" - -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: JAVASCRIPT_BRIDGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["callHandler"]) - -let JAVASCRIPT_BRIDGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME) = {}; -\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {}; -window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - var _callHandlerID = setTimeout(function(){}); - window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} ); - return new Promise(function(resolve, reject) { - window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject}; - }); -}; -\(WEB_MESSAGE_LISTENER_JS_SOURCE) -\(UTIL_JS_SOURCE) -""" - -let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; +public class JavaScriptBridgeJS { + private static var _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" + public static func set_JAVASCRIPT_BRIDGE_NAME(bridgeName: String) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName + } + public static func get_JAVASCRIPT_BRIDGE_NAME() -> String { + return _JAVASCRIPT_BRIDGE_NAME + } + + public static let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" + + public static let VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET" -let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util" + public static func JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: String, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + let source = JAVASCRIPT_BRIDGE_JS_SOURCE().replacingOccurrences(of: VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, with: expectedBridgeSecret) + return PluginScript( + groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: source, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: ["callHandler"]) + } -/* - https://github.com/github/fetch/blob/master/fetch.js - */ -let UTIL_JS_SOURCE = """ -\(JAVASCRIPT_UTIL_VAR_NAME) = { - support: { - searchParams: 'URLSearchParams' in window, - iterable: 'Symbol' in window && 'iterator' in Symbol, - blob: - 'FileReader' in window && - 'Blob' in window && - (function() { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - })(), - formData: 'FormData' in window, - arrayBuffer: 'ArrayBuffer' in window - }, - isDataView: function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj); - }, - fileReaderReady: function(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result); - }; - reader.onerror = function() { - reject(reader.error); - }; - }); - }, - readBlobAsArrayBuffer: function(blob) { - var reader = new FileReader(); - var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader); - reader.readAsArrayBuffer(blob); - return promise; - }, - convertBodyToArrayBuffer: function(body) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ]; - var isArrayBufferView = null; - if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) { - isArrayBufferView = - ArrayBuffer.isView || - function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + public static func JAVASCRIPT_BRIDGE_JS_SOURCE() -> String { + return """ + window.\(get_JAVASCRIPT_BRIDGE_NAME()) = {}; + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME()) = {}; + (function(window) { + var bridgeSecret = '\(VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET)'; + var _JSON_stringify; + var _Array_slice; + var _setTimeout; + var _Promise; + var _UserMessageHandler; + var _postMessage; + try { + _JSON_stringify = window.JSON.stringify; + _Array_slice = window.Array.prototype.slice; + _Array_slice.call = window.Function.prototype.call; + _setTimeout = window.setTimeout; + _Promise = window.Promise; + _UserMessageHandler = window.webkit.messageHandlers['callHandler']; + _postMessage = _UserMessageHandler.postMessage; + _postMessage.call = window.Function.prototype.call; + } catch (_) { return; } + window.\(get_JAVASCRIPT_BRIDGE_NAME()).callHandler = function() { + var _windowId = \(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()); + var _callHandlerID = _setTimeout(function(){}); + _postMessage.call(_UserMessageHandler, { + 'handlerName': arguments[0], + '_callHandlerID': _callHandlerID, + '_bridgeSecret': bridgeSecret, + 'args': _JSON_stringify(_Array_slice.call(arguments, 1)), + '_windowId': _windowId + }); + return new _Promise(function(resolve, reject) { + try { + (window.top === window ? window : window.top).\(get_JAVASCRIPT_BRIDGE_NAME())[_callHandlerID] = {resolve: resolve, reject: reject}; + } catch (e) { + resolve(); + } + }); }; - } + })(window); + \(WebMessageListenerJS.WEB_MESSAGE_LISTENER_JS_SOURCE()) + \(UTIL_JS_SOURCE()) + """ + } - var bodyUsed = false; + public static let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; - this._bodyInit = body; - if (!body) { - this._bodyText = ''; - } else if (typeof body === 'string') { - this._bodyText = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString(); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer); - this._bodyInit = new Blob([this._bodyArrayBuffer]); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body); - } else { - this._bodyText = body = Object.prototype.toString.call(body); - } + public static func JAVASCRIPT_UTIL_VAR_NAME() -> String { + return "window.\(get_JAVASCRIPT_BRIDGE_NAME())._Util" + } - this.blob = function () { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob); - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])); - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob'); - } else { - return Promise.resolve(new Blob([this._bodyText])); + /* + https://github.com/github/fetch/blob/master/fetch.js + */ + public static func UTIL_JS_SOURCE() -> String { + return """ + \(JAVASCRIPT_UTIL_VAR_NAME()) = { + support: { + searchParams: 'URLSearchParams' in window, + iterable: 'Symbol' in window && 'iterator' in Symbol, + blob: + 'FileReader' in window && + 'Blob' in window && + (function() { + try { + new Blob(); + return true; + } catch (e) { + return false; + } + })(), + formData: 'FormData' in window, + arrayBuffer: 'ArrayBuffer' in window + }, + isDataView: function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj); + }, + fileReaderReady: function(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }); + }, + readBlobAsArrayBuffer: function(blob) { + var reader = new FileReader(); + var promise = \(JAVASCRIPT_UTIL_VAR_NAME()).fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise; + }, + convertBodyToArrayBuffer: function(body) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + var isArrayBufferView = null; + if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer) { + isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + }; + } + + var bodyUsed = false; + + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME()).isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + this.blob = function () { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob); + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])); + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob'); + } else { + return Promise.resolve(new Blob([this._bodyText])); + } + }; + + if (this._bodyArrayBuffer) { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ); + } else { + return Promise.resolve(this._bodyArrayBuffer); + } + } + return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME()).readBlobAsArrayBuffer); + }, + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + convertBodyRequest: function(body) { + if (body == null) { + return new Promise((resolve, reject) => resolve(null)); + } + if (\(JAVASCRIPT_UTIL_VAR_NAME()).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && body instanceof URLSearchParams)) { + return new Promise((resolve, reject) => resolve(body.toString())); + } + if (window.Response != null) { + return new Response(body).arrayBuffer().then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + } + return \(JAVASCRIPT_UTIL_VAR_NAME()).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + }, + arrayBufferToString: function(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') >= 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.substr(2, 40); + return 'multipart/form-data; boundary=' + boundary; + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + for (var header of headers.keys()) { + var value = headers.get(header); + headersObj[header] = value; + } + return headersObj; + }, + convertJsonToHeaders: function(headersJson) { + return new Headers(headersJson); + }, + 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; } }; - - if (this._bodyArrayBuffer) { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (ArrayBuffer.isView(this._bodyArrayBuffer)) { - return Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer.byteOffset, - this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength - ) - ); - } else { - return Promise.resolve(this._bodyArrayBuffer); - } - } - return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer); - }, - isString: function(variable) { - return typeof variable === 'string' || variable instanceof String; - }, - convertBodyRequest: function(body) { - if (body == null) { - return new Promise((resolve, reject) => resolve(null)); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) { - return new Promise((resolve, reject) => resolve(body.toString())); - } - if (window.Response != null) { - return new Response(body).arrayBuffer().then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - } - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - }, - arrayBufferToString: function(arrayBuffer) { - var uint8Array = new Uint8Array(arrayBuffer); - return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); - }, - isBodyFormData: function(bodyString) { - return bodyString.indexOf('------WebKitFormBoundary') >= 0; - }, - getFormDataContentType: function(bodyString) { - var boundary = bodyString.substr(2, 40); - return 'multipart/form-data; boundary=' + boundary; - }, - convertHeadersToJson: function(headers) { - var headersObj = {}; - for (var header of headers.keys()) { - var value = headers.get(header); - headersObj[header] = value; - } - return headersObj; - }, - convertJsonToHeaders: function(headersJson) { - return new Headers(headersJson); - }, - 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; + """ } -}; -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift index 828de9ef5..18a5e496b 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift @@ -7,33 +7,44 @@ import Foundation -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource" - -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_LOAD_RESOURCE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_LOAD_RESOURCE_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true; -(function() { - var observer = new PerformanceObserver(function(list) { - list.getEntries().forEach(function(entry) { - if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) { - var resource = { - "url": entry.name, - "initiatorType": entry.initiatorType, - "startTime": entry.startTime, - "duration": entry.duration - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource); - } - }); - }); - observer.observe({entryTypes: ['resource']}); -})(); -""" +public class OnLoadResourceJS { + + public static let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnLoadResource" + } + + public static func ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_LOAD_RESOURCE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) = true; + (function() { + var observer = new PerformanceObserver(function(list) { + list.getEntries().forEach(function(entry) { + if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == true) { + var resource = { + "url": entry.name, + "initiatorType": entry.initiatorType, + "startTime": entry.startTime, + "duration": entry.duration + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onLoadResource", resource); + } + }); + }); + observer.observe({entryTypes: ['resource']}); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift index 4418c042a..2c6b29d24 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift @@ -7,27 +7,33 @@ import Foundation -let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT" - -let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onScrollChanged"]) - -let ON_SCROLL_CHANGED_EVENT_JS_SOURCE = """ -(function(){ - document.addEventListener('scroll', function(e) { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers["onScrollChanged"].postMessage( - { - x: window.scrollX, - y: window.scrollY, - _windowId: _windowId - } - ); - }); -})(); -""" +public class OnScrollChangedJS { + + public static let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_SCROLL_CHANGED_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + document.addEventListener('scroll', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onScrollChanged', { + 'x': window.scrollX, + 'y': window.scrollY + } + ); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift index 345b5eced..d9221d10e 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_WINDOW_BLUR_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('blur', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur'); - }); -})(); -""" +public class OnWindowBlurEventJS { + + public static let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_BLUR_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('blur', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowBlur'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift index 9ebda3e96..b08bb0889 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('focus', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus'); - }); -})(); -""" +public class OnWindowFocusEventJS { + + public static let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_FOCUS_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('focus', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowFocus'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift index a1c6637d7..d84c14ee3 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift @@ -7,25 +7,34 @@ import Foundation -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = ""; -(function() { - var metaTagNodes = document.head.getElementsByTagName('meta'); - for (var i = 0; i < metaTagNodes.length; i++) { - var metaTagNode = metaTagNodes[i]; - if (metaTagNode.name === "viewport") { - window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content; - } +public class OriginalViewPortMetaTagContentJS { + + public static let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE(), + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = ""; + (function() { + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + if (metaTagNode.name === "viewport") { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = metaTagNode.content; + } + } + })(); + """ } -})(); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift index 5ac12691f..fbf29f50f 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift @@ -7,18 +7,26 @@ import Foundation -let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" - -let PRINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PRINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let PRINT_JS_SOURCE = """ -window.print = function() { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href); +public class PrintJS { + + public static let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" + + public static func PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PRINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func PRINT_JS_SOURCE() -> String { + return """ + window.print = function() { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onPrintRequest", window.location.href); + } + """ + } } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift index d2ef3e2a5..27c076be2 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift @@ -8,20 +8,25 @@ import Foundation import WebKit -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT" - -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PROMISE_POLYFILL_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -// https://github.com/tildeio/rsvp.js -let PROMISE_POLYFILL_JS_SOURCE = """ -if (window.Promise == null) { - !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PROMISE_POLYFILL_JS_SOURCE, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + // https://github.com/tildeio/rsvp.js + public static let PROMISE_POLYFILL_JS_SOURCE = """ + if (window.Promise == null) { + !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: NOT_SUPPORT_ZOOM_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let NOT_SUPPORT_ZOOM_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func SUPPORT_ZOOM_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift index 184458413..e41494d88 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift @@ -7,4 +7,10 @@ import Foundation -let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels" +public class WebMessageChannelJS { + + public static func WEB_MESSAGE_CHANNELS_VARIABLE_NAME() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._webMessageChannels" + } + +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift index ab8edfaed..6740765ac 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift @@ -7,110 +7,37 @@ import Foundation -let WEB_MESSAGE_LISTENER_JS_SOURCE = """ -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.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); -}; -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); - } -}; - -window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) { - // replace ipv4 address if any - var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); - if (ipv4) { - ip_string = ipv4[1]; - ipv4 = ipv4[2].match(/[0-9]+/g); - for (var i = 0;i < 4;i ++) { - var byte = parseInt(ipv4[i],10); - ipv4[i] = ("0" + byte.toString(16)).substr(-2); - } - ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; - } - - // take care of leading and trailing :: - ip_string = ip_string.replace(/^:|:$/g, ''); - - var ipv6 = ip_string.split(':'); - - for (var i = 0; i < ipv6.length; i ++) { - var hex = ipv6[i]; - if (hex != "") { - // normalize leading zeros - ipv6[i] = ("0000" + hex).substr(-4); - } - else { - // normalize grouped zeros :: - hex = []; - for (var j = ipv6.length; j <= 8; j ++) { - hex.push('0000'); +public class WebMessageListenerJS { + + public static func WEB_MESSAGE_LISTENER_JS_SOURCE() -> String { + return """ + 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.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessageListenerPostMessageReceived', {jsObjectName: this.jsObjectName, message: message}); + }; + FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { + if (listener == null) { + return; } - ipv6[i] = hex.join(':'); - } - } - - return ipv6.join(':'); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) { - for (var rule of allowedOriginRules) { - if (rule === "*") { - return true; - } - if (scheme == null || scheme === "") { - continue; - } - if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { - continue; - } - var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; - var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; - var IPv6 = null; - if (rule.host != null && rule.host[0] === "[") { - try { - IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); - } catch {} - } - var hostIPv6 = null; - try { - hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host); - } catch {} - - var schemeAllowed = scheme == rule.scheme; - - var hostAllowed = rule.host == null || - rule.host === "" || - host === rule.host || - (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || - (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); - - var portAllowed = rulePort === currentPort; - - if (schemeAllowed && hostAllowed && portAllowed) { - return true; - } + 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); + } + }; + """ } - return false; } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift index 8b3a1879f..173167416 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift @@ -7,13 +7,20 @@ import Foundation -let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" - -let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId" - -let WINDOW_ID_INITIALIZE_JS_SOURCE = """ -(function() { - \(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); - return \(WINDOW_ID_VARIABLE_JS_SOURCE); -})() -""" +public class WindowIdJS { + + public static let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" + + public static func WINDOW_ID_VARIABLE_JS_SOURCE() -> String { + return "window._\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_windowId" + } + + public static func WINDOW_ID_INITIALIZE_JS_SOURCE() -> String { + return """ + (function() { + \(WINDOW_ID_VARIABLE_JS_SOURCE()) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); + return \(WINDOW_ID_VARIABLE_JS_SOURCE()); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift index 7514dd4f5..d2000c8b1 100644 --- a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift +++ b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift @@ -55,6 +55,7 @@ public class PrintJobChannelDelegate: ChannelDelegate { } deinit { + debugPrint("PrintJobChannelDelegate - dealloc") dispose() } } diff --git a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift index 9b76a4085..400f7160d 100644 --- a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift +++ b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift @@ -24,6 +24,7 @@ public class PrintJobController: NSObject, Disposable { var channelDelegate: PrintJobChannelDelegate? var state = PrintJobState.created var creationTime = Int64(Date().timeIntervalSince1970 * 1000) + var disposeWhenDidRun = false private var completionHandler: PrintJobController.CompletionHandler? public typealias CompletionHandler = (_ printOperation: NSPrintOperation, @@ -36,11 +37,9 @@ public class PrintJobController: NSObject, Disposable { super.init() self.job = job self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) } public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) { @@ -57,11 +56,16 @@ public class PrintJobController: NSObject, Disposable { @objc func printOperationDidRun(printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) { - state = success ? .completed : .canceled - channelDelegate?.onComplete(completed: success, error: nil) - if let completionHandler = completionHandler { - completionHandler(printOperation, success, contextInfo) - self.completionHandler = nil + DispatchQueue.main.async { [weak self] in + self?.state = success ? .completed : .canceled + self?.channelDelegate?.onComplete(completed: success, error: nil) + if let completionHandler = self?.completionHandler { + completionHandler(printOperation, success, contextInfo) + self?.completionHandler = nil + } + if let disposeWhenDidRun = self?.disposeWhenDidRun, disposeWhenDidRun { + self?.dispose() + } } } @@ -73,7 +77,7 @@ public class PrintJobController: NSObject, Disposable { return PrintJobInfo.init(fromPrintJobController: self) } - public func disposeNoDismiss() { + public func dispose() { channelDelegate?.dispose() channelDelegate = nil completionHandler = nil @@ -82,12 +86,7 @@ public class PrintJobController: NSObject, Disposable { plugin = nil } - public func dispose() { - channelDelegate?.dispose() - channelDelegate = nil - completionHandler = nil - job = nil - plugin?.printJobManager?.jobs[id] = nil - plugin = nil + deinit { + debugPrint("PrintJobController - dealloc") } } diff --git a/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift b/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift new file mode 100644 index 000000000..082d55bf2 --- /dev/null +++ b/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift @@ -0,0 +1,248 @@ +// +// ProxyManager.swift +// flutter_inappwebview +// + +import Foundation +import WebKit +import FlutterMacOS + +@available(macOS 14.0, *) +public class ProxyManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_proxycontroller" + + private var plugin: InAppWebViewFlutterPlugin? + + init(plugin: InAppWebViewFlutterPlugin) { + super.init(channel: FlutterMethodChannel(name: ProxyManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) + self.plugin = plugin + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? [String: Any] + switch call.method { + case "setProxyOverride": + if let args = arguments?["settings"] as? [String:Any?], + let settings = ProxySettings.fromMap(map: args) { + setProxyOverride(settings) + result(true) + } else { + result(false) + } + break + case "clearProxyOverride": + clearProxyOverride() + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func setProxyOverride(_ settings: ProxySettings) { + let proxyConfigurations = settings.toProxyConfigurations() + WKWebsiteDataStore.default().proxyConfigurations = proxyConfigurations + WKWebsiteDataStore.nonPersistent().proxyConfigurations = proxyConfigurations + } + + public func clearProxyOverride() { + WKWebsiteDataStore.default().proxyConfigurations = [] + WKWebsiteDataStore.nonPersistent().proxyConfigurations = [] + } + + public override func dispose() { + super.dispose() + plugin = nil + } + + deinit { + debugPrint("ProxyManager - dealloc") + dispose() + } +} + +@available(macOS 14.0, *) +public class ProxySettings { + var proxyRules: [ProxyRule] + + init( + proxyRules: [ProxyRule] + ) { + self.proxyRules = proxyRules + } + + public static func fromMap(map: [String:Any?]?) -> ProxySettings? { + guard let map = map else { + return nil + } + return ProxySettings( + proxyRules: (map["proxyRules"] as! [[String:Any?]]).map { ProxyRule.fromMap(map: $0)! } + ) + } + + public func toProxyConfigurations() -> [ProxyConfiguration] { + var proxyConfigurations: [ProxyConfiguration] = [] + for rule in proxyRules { + if let proxyConfiguration = rule.toProxyConfiguration() { + proxyConfigurations.append(proxyConfiguration) + } + } + return proxyConfigurations + } +} + +@available(macOS 14.0, *) +public class ProxyRule { + var url: String + var allowFailover: Bool? + var excludedDomains: [String]? + var matchDomains: [String]? + var username: String? + var password: String? + var relayHop1: ProxyRelayHop? + var relayHop2: ProxyRelayHop? + + init( + url: String, + allowFailover: Bool?, + excludedDomains: [String]?, + matchDomains: [String]?, + username: String?, + password: String?, + relayHop1: ProxyRelayHop?, + relayHop2: ProxyRelayHop? + ) { + self.url = url + self.allowFailover = allowFailover + self.excludedDomains = excludedDomains + self.matchDomains = matchDomains + self.username = username + self.password = password + self.relayHop1 = relayHop1 + self.relayHop2 = relayHop2 + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRule? { + guard let map = map else { + return nil + } + return ProxyRule( + url: map["url"] as! String, + allowFailover: map["allowFailover"] as? Bool, + excludedDomains: map["excludedDomains"] as? [String], + matchDomains: map["matchDomains"] as? [String], + username: map["username"] as? String, + password: map["password"] as? String, + relayHop1: ProxyRelayHop.fromMap(map: map["relayHop1"] as? [String:Any?]), + relayHop2: ProxyRelayHop.fromMap(map: map["relayHop2"] as? [String:Any?]) + ) + } + + public func toProxyConfiguration() -> ProxyConfiguration? { + guard let endpointUrl = URL(string: url.contains("://") ? url : "http://" + url), + let port: NWEndpoint.Port = .init(rawValue: UInt16(endpointUrl.port ?? 80)), + let host = endpointUrl.host else { + return nil + } + + var endpointHost = NWEndpoint.Host(host) + if let ipv4 = IPv4Address(host) { + endpointHost = .ipv4(ipv4) + } else if let ipv6 = IPv6Address(host) { + endpointHost = .ipv6(ipv6) + } + let endpoint = NWEndpoint.hostPort(host: endpointHost, port: port) + var proxyConfiguration: ProxyConfiguration + let proxyRelayHops: [ProxyRelayHop] = [relayHop1, relayHop2].filter({ $0 != nil }).map({ $0! }) + if !proxyRelayHops.isEmpty { + proxyConfiguration = ProxyConfiguration(relayHops: proxyRelayHops.compactMap({ $0.toRelayHop() })) + } else { + proxyConfiguration = endpointUrl.scheme?.lowercased() == "socks5" ? + ProxyConfiguration(socksv5Proxy: endpoint) : + ProxyConfiguration(httpCONNECTProxy: endpoint, tlsOptions: endpointUrl.scheme?.lowercased() == "https" ? .init() : nil) + } + + if let allowFailover = allowFailover { + proxyConfiguration.allowFailover = allowFailover + } + if let excludedDomains = excludedDomains { + proxyConfiguration.excludedDomains = excludedDomains + } + if let matchDomains = matchDomains { + proxyConfiguration.matchDomains = matchDomains + } + if let username = username, let password = password { + proxyConfiguration.applyCredential(username: username, password: password) + } + return proxyConfiguration + } +} + +@available(macOS 14.0, *) +public class ProxyRelayHop { + var http3RelayEndpoint: String? + var http2RelayEndpoint: String? + var additionalHTTPHeaders: [String:String]? + + init( + http3RelayEndpoint: String, + http2RelayEndpoint: String?, + additionalHTTPHeaders: [String:String]? + ) { + self.http3RelayEndpoint = http3RelayEndpoint + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + init( + http2RelayEndpoint: String, + additionalHTTPHeaders: [String:String]? + ) { + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRelayHop? { + guard let map = map else { + return nil + } + let http3RelayEndpoint = map["http3RelayEndpoint"] as? String + let http2RelayEndpoint = map["http2RelayEndpoint"] as? String + let additionalHTTPHeaders = map["additionalHTTPHeaders"] as? [String:String] + if http3RelayEndpoint == nil, http2RelayEndpoint == nil { + return nil + } + if http3RelayEndpoint != nil { + return ProxyRelayHop( + http3RelayEndpoint: http3RelayEndpoint!, + http2RelayEndpoint: http2RelayEndpoint, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + return ProxyRelayHop( + http2RelayEndpoint: http2RelayEndpoint!, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + + public func toRelayHop() -> ProxyConfiguration.RelayHop? { + if let http3RelayEndpoint = http3RelayEndpoint, + let url = URL(string: http3RelayEndpoint) { + var http2Endpoint: NWEndpoint? = nil + if let http2RelayEndpoint = http2RelayEndpoint, + let url2 = URL(string: http2RelayEndpoint) { + http2Endpoint = NWEndpoint.url(url2) + } + return ProxyConfiguration.RelayHop(http3RelayEndpoint: NWEndpoint.url(url), + http2RelayEndpoint: http2Endpoint, + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + if let http2RelayEndpoint = http2RelayEndpoint, + let url = URL(string: http2RelayEndpoint) { + return ProxyConfiguration.RelayHop(http2RelayEndpoint: NWEndpoint.url(url), + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + return nil + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift b/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift new file mode 100644 index 000000000..eb5a2c973 --- /dev/null +++ b/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift @@ -0,0 +1,41 @@ +// +// JavaScriptHandlerFunctionData.swift +// Pods +// +// Created by Lorenzo Pichilli on 27/10/24. +// +public class JavaScriptHandlerFunctionData: NSObject { + var args: String + var isMainFrame: Bool + var origin: String + var requestUrl: String + + public init(args: String, isMainFrame: Bool, origin: String, requestUrl: String) { + self.args = args + self.isMainFrame = isMainFrame + self.origin = origin + self.requestUrl = requestUrl + } + + public static func fromMap(map: [String:Any?]?) -> JavaScriptHandlerFunctionData? { + guard let map = map else { + return nil + } + + return JavaScriptHandlerFunctionData( + args: map["args"] as! String, + isMainFrame: map["isMainFrame"] as! Bool, + origin: map["origin"] as! String, + requestUrl: map["requestUrl"] as! String + ) + } + + public func toMap () -> [String:Any?] { + return [ + "args": args, + "isMainFrame": isMainFrame, + "origin": origin, + "requestUrl": requestUrl + ] + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift b/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift index 7218099b6..096f5d883 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift @@ -16,8 +16,8 @@ public class PluginScript: UserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -36,8 +36,9 @@ public class PluginScript: UserScript { } @available(macOS 11.0, *) - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, + allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -47,6 +48,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, requiredInAllContentWorlds: Bool? = nil, + allowedOriginRules: [String]? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { if #available(macOS 11.0, *) { return PluginScript( @@ -55,6 +57,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -64,6 +67,7 @@ public class PluginScript: UserScript { source: source ?? self.source, injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -75,6 +79,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, contentWorld: WKContentWorld? = nil, + allowedOriginRules: [String]? = nil, requiredInAllContentWorlds: Bool? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { return PluginScript( @@ -83,6 +88,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: contentWorld ?? self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -95,6 +101,7 @@ public class PluginScript: UserScript { lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && lhs.contentWorld == rhs.contentWorld && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } else { @@ -102,6 +109,7 @@ public class PluginScript: UserScript { lhs.source == rhs.source && lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift b/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift index d350836e3..cd8559a1a 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift @@ -10,6 +10,7 @@ import WebKit public class UserScript: WKUserScript { var groupName: String? + var allowedOriginRules: [String]? private var contentWorldWrapper: Any? @available(macOS 11.0, *) @@ -27,9 +28,10 @@ public class UserScript: WKUserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) self.groupName = groupName + self.allowedOriginRules = allowedOriginRules } @available(macOS 11.0, *) @@ -39,10 +41,34 @@ public class UserScript: WKUserScript { } @available(macOS 11.0, *) - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) self.groupName = groupName self.contentWorld = contentWorld + self.allowedOriginRules = allowedOriginRules + } + + static private func wrapSourceCodeAddChecks(source: String, allowedOriginRules: [String]?) -> String { + var ifStatement = "if (" + if let allowedOriginRules = allowedOriginRules, !allowedOriginRules.contains("*") { + if allowedOriginRules.isEmpty { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return "" + } + var jsRegExpArray = "[" + for allowedOriginRule in allowedOriginRules { + if jsRegExpArray.count > 1 { + jsRegExpArray += "," + } + jsRegExpArray += "new RegExp('\(allowedOriginRule.replacingOccurrences(of: "\'", with: "\\'"))')" + } + if jsRegExpArray.count > 1 { + jsRegExpArray += "]" + ifStatement += "\(jsRegExpArray).some(function(rx) { return rx.test(window.location.origin); })" + } + } + return ifStatement.count > 4 ? "\(ifStatement)) { \(source) }" : source } public static func fromMap(map: [String:Any?]?, windowId: Int64?) -> UserScript? { @@ -58,14 +84,16 @@ public class UserScript: WKUserScript { source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, forMainFrameOnly: map["forMainFrameOnly"] as! Bool, - in: contentWorld + in: contentWorld, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } return UserScript( groupName: map["groupName"] as? String, source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, - forMainFrameOnly: map["forMainFrameOnly"] as! Bool + forMainFrameOnly: map["forMainFrameOnly"] as! Bool, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift b/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift index 21d0b62cd..ba0f49e8b 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift @@ -166,7 +166,7 @@ extension WKUserContentController { } } if let windowId = contentWorld.windowId { - generatedCode += "\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(String(windowId));\n" + generatedCode += "\(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()) = \(String(windowId));\n" } return generatedCode + "\n" + source } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift b/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift index b7a6e53ca..7cb2f3b58 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift @@ -33,10 +33,10 @@ public class WebMessagePort: NSObject { let index = name == "port1" ? 0 : 1 webView.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).onmessage = function (event) { - window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessagePortMessageReceived', { "webMessageChannelId": "\(webMessageChannel.id)", "index": \(String(index)), "message": { @@ -74,14 +74,14 @@ public class WebMessagePort: NSObject { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString)); } @@ -104,7 +104,7 @@ public class WebMessagePort: NSObject { if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView { let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).close(); } diff --git a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index 92140fe1e..c314423bf 100644 --- a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -34,7 +34,7 @@ public class WebAuthenticationSession: NSObject, ASWebAuthenticationPresentation self.session = session } let channel = FlutterMethodChannel(name: WebAuthenticationSession.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger) + binaryMessenger: plugin.registrar.messenger) self.channelDelegate = WebAuthenticationSessionChannelDelegate(webAuthenticationSession: self, channel: channel) } diff --git a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 102438fff..fb153f84a 100644 --- a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -18,7 +18,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Resources/PrivacyInfo.xcprivacy b/flutter_inappwebview_macos/macos/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..0eca193ea --- /dev/null +++ b/flutter_inappwebview_macos/macos/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + \ No newline at end of file diff --git a/flutter_inappwebview_macos/macos/flutter_inappwebview_macos.podspec b/flutter_inappwebview_macos/macos/flutter_inappwebview_macos.podspec index 3f43bea64..7589c5f44 100644 --- a/flutter_inappwebview_macos/macos/flutter_inappwebview_macos.podspec +++ b/flutter_inappwebview_macos/macos/flutter_inappwebview_macos.podspec @@ -18,10 +18,11 @@ A new Flutter plugin project. s.resources = 'Storyboards/**/*.storyboard' s.public_header_files = 'Classes/**/*.h' s.dependency 'FlutterMacOS' + s.resource_bundles = {'flutter_inappwebview_macos_privacy' => ['Resources/PrivacyInfo.xcprivacy']} - s.platform = :osx, '10.11' + s.platform = :osx, '10.14' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.dependency 'OrderedSet', '~>5.0' + s.dependency 'OrderedSet', '~>6.0.3' end diff --git a/flutter_inappwebview_macos/pubspec.yaml b/flutter_inappwebview_macos/pubspec.yaml index e87894cd5..8688fea92 100644 --- a/flutter_inappwebview_macos/pubspec.yaml +++ b/flutter_inappwebview_macos/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview_macos description: macOS implementation of the flutter_inappwebview plugin. -version: 1.0.11 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_macos issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,19 +14,20 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.0.10 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 - plugin_platform_interface: ^2.0.2 + flutter_lints: ^4.0.0 + plugin_platform_interface: ^2.1.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter_inappwebview_platform_interface/CHANGELOG.md b/flutter_inappwebview_platform_interface/CHANGELOG.md index 6aba96803..d53864cad 100644 --- a/flutter_inappwebview_platform_interface/CHANGELOG.md +++ b/flutter_inappwebview_platform_interface/CHANGELOG.md @@ -1,3 +1,65 @@ +## 1.4.0-beta.3 + +- Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` + +## 1.4.0-beta.2 + +- Updated `flutter_inappwebview_internal_annotations` dependency from `^1.1.1` to `^1.2.0` +- Updated `fromMap` static method and `toMap` method implementations +- Updated all WebView events with return type `Future` to type `FutureOr` in order to not force the usage of `async` keyword +- Added `byName`, `name`, `asNameMap` custom enum classes methods +- Added `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled`, `alpha`, `isUserInteractionEnabled`, `handleAcceleratorKeyPressed` properties to `InAppWebViewSettings` +- Added `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` methods to `PlatformWebViewEnvironment` class +- Added `isInterfaceSupported`, `setInputMethodEnabled`, `hideInputMethod`, `showInputMethod` methods to `PlatformInAppWebViewController` class +- Added `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties to `WebViewEnvironmentSettings` +- Added `onDownloadStarting` WebView event and deprecated `onDownloadStartRequest` event +- Added `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` events to `PlatformWebViewEnvironment` class +- Added `onAcceleratorKeyPressed` WebView event +- Fixed missing PrintJobOrientation android values + +## 1.4.0-beta.1 + +- Updated static `fromMap` implementation for some classes +- Updated `kJavaScriptHandlerForbiddenNames` list +- Added `PlatformInAppLocalhostServer.onData` parameter to set a custom on data server callback +- Added `javaScriptBridgeEnabled`, `javaScriptBridgeOriginAllowList`, `javaScriptBridgeForMainFrameOnly`, `pluginScriptsOriginAllowList`, `pluginScriptsForMainFrameOnly`, `javaScriptHandlersOriginAllowList`, `javaScriptHandlersForMainFrameOnly`, `scrollMultiplier` InAppWebViewSettings parameters +- Added `setJavaScriptBridgeName`, `getJavaScriptBridgeName` static WebView controller methods +- Added `onProcessFailed` WebView event +- Added `regexToAllowSyncUrlLoading` Android-specific property to `InAppWebViewSettings` +- Added `JavaScriptHandlerFunctionData` type +- Deprecated `JavaScriptHandlerCallback` type in favor of `JavaScriptHandlerFunction` type +- Deprecated `InAppWebViewSettings.forceDark` and `InAppWebViewSettings.forceDarkStrategy` Android-only properties in favor of `InAppWebViewSettings.algorithmicDarkeningAllowed` +- Fixed X509Certificate PEM base64 decoding + +## 1.3.0+1 + +- Fixed `X509Certificate.toMap` method + +## 1.3.0 + +- Added `WebViewEnvironment.customSchemeRegistrations` parameter for Windows +- Added `CustomSchemeRegistration` type +- Updated docs + +## 1.2.0 + +- Updated `Uint8List` conversion inside `fromMap` methods + +## 1.1.1 + +- Updated permission models for Windows platform + +## 1.1.0+1 + +- Updated docs and pubspec.yaml + +## 1.1.0 + +- Added `PlatformWebViewEnvironment` class +- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. +- Removed unsupported feature `WebViewFeature.SUPPRESS_ERROR_PAGE` + ## 1.0.10 - Merged "Added == operator and hashCode to WebUri" [#1941](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1941) (thanks to [daisukeueta](https://github.com/daisukeueta)) diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart index 400296a19..8ed203e92 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart @@ -5,6 +5,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'platform_chrome_safari_browser.dart'; import 'chrome_safari_browser_menu_item.dart'; import '../web_uri.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_action_button.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart index ef981f4bc..9a2ab89d6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart @@ -46,21 +46,24 @@ class ChromeSafariBrowserActionButton { this.shouldTint = false}); ///Gets a possible [ChromeSafariBrowserActionButton] instance from a [Map] value. - static ChromeSafariBrowserActionButton? fromMap(Map? map) { + static ChromeSafariBrowserActionButton? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserActionButton( description: map['description'], - icon: map['icon'], + icon: Uint8List.fromList(map['icon'].cast()), id: map['id'], ); - instance.shouldTint = map['shouldTint']; + if (map['shouldTint'] != null) { + instance.shouldTint = map['shouldTint']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "description": description, "icon": icon, diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart index f3a625798..e0a8c361d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../types/ui_image.dart'; import 'platform_chrome_safari_browser.dart'; import '../web_uri.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_menu_item.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart index 9bb9f017a..ec9618d04 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart @@ -44,23 +44,25 @@ class ChromeSafariBrowserMenuItem { this.onClick}); ///Gets a possible [ChromeSafariBrowserMenuItem] instance from a [Map] value. - static ChromeSafariBrowserMenuItem? fromMap(Map? map) { + static ChromeSafariBrowserMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserMenuItem( id: map['id'], - image: UIImage.fromMap(map['image']?.cast()), + image: UIImage.fromMap(map['image']?.cast(), + enumMethod: enumMethod), label: map['label'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, - "image": image?.toMap(), + "image": image?.toMap(enumMethod: enumMethod), "label": label, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart index 6404b07a9..5da88d85e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../types/android_resource.dart'; import '../web_uri.dart'; import 'platform_chrome_safari_browser.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_secondary_toolbar.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart index 7b1a7a3e8..3b4a72c59 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart @@ -35,27 +35,32 @@ class ChromeSafariBrowserSecondaryToolbar { {this.clickableIDs = const [], required this.layout}); ///Gets a possible [ChromeSafariBrowserSecondaryToolbar] instance from a [Map] value. - static ChromeSafariBrowserSecondaryToolbar? fromMap( - Map? map) { + static ChromeSafariBrowserSecondaryToolbar? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSecondaryToolbar( - layout: AndroidResource.fromMap(map['layout']?.cast())!, + layout: AndroidResource.fromMap(map['layout']?.cast(), + enumMethod: enumMethod)!, ); - instance.clickableIDs = - List.from( - map['clickableIDs'].map((e) => - ChromeSafariBrowserSecondaryToolbarClickableID.fromMap( - e?.cast())!)); + if (map['clickableIDs'] != null) { + instance.clickableIDs = + List.from( + map['clickableIDs'].map((e) => + ChromeSafariBrowserSecondaryToolbarClickableID.fromMap( + e?.cast(), + enumMethod: enumMethod)!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "clickableIDs": clickableIDs.map((e) => e.toMap()).toList(), - "layout": layout.toMap(), + "clickableIDs": + clickableIDs.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "layout": layout.toMap(enumMethod: enumMethod), }; } @@ -93,20 +98,22 @@ class ChromeSafariBrowserSecondaryToolbarClickableID { ///Gets a possible [ChromeSafariBrowserSecondaryToolbarClickableID] instance from a [Map] value. static ChromeSafariBrowserSecondaryToolbarClickableID? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSecondaryToolbarClickableID( - id: AndroidResource.fromMap(map['id']?.cast())!, + id: AndroidResource.fromMap(map['id']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "id": id.toMap(), + "id": id.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart index b8d6610ce..7d854d45b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart @@ -15,17 +15,20 @@ import '../types/ui_event_attribution.dart'; import '../util.dart'; import 'android/chrome_custom_tabs_options.dart'; import 'apple/safari_options.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_settings.g.dart'; TrustedWebActivityDisplayMode? _deserializeDisplayMode( - Map? displayMode) { + Map? displayMode, + {EnumMethod? enumMethod}) { if (displayMode == null) { return null; } switch (displayMode["type"]) { case "IMMERSIVE_MODE": - return TrustedWebActivityImmersiveDisplayMode.fromMap(displayMode); + return TrustedWebActivityImmersiveDisplayMode.fromMap(displayMode, + enumMethod: enumMethod); case "DEFAULT_MODE": default: return TrustedWebActivityDefaultDisplayMode(); diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart index 4ec954015..577c4fac2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart @@ -252,19 +252,24 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { } ///Gets a possible [ChromeSafariBrowserSettings] instance from a [Map] value. - static ChromeSafariBrowserSettings? fromMap(Map? map) { + static ChromeSafariBrowserSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSettings( activityButton: ActivityButton.fromMap( - map['activityButton']?.cast()), - displayMode: _deserializeDisplayMode(map['displayMode']), + map['activityButton']?.cast(), + enumMethod: enumMethod), + displayMode: + _deserializeDisplayMode(map['displayMode'], enumMethod: enumMethod), eventAttribution: UIEventAttribution.fromMap( - map['eventAttribution']?.cast()), + map['eventAttribution']?.cast(), + enumMethod: enumMethod), exitAnimations: map['exitAnimations'] != null - ? List.from(map['exitAnimations'] - .map((e) => AndroidResource.fromMap(e?.cast())!)) + ? List.from(map['exitAnimations'].map((e) => + AndroidResource.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, navigationBarColor: map['navigationBarColor'] != null ? UtilColor.fromStringRepresentation(map['navigationBarColor']) @@ -283,8 +288,9 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { ? UtilColor.fromStringRepresentation(map['secondaryToolbarColor']) : null, startAnimations: map['startAnimations'] != null - ? List.from(map['startAnimations'] - .map((e) => AndroidResource.fromMap(e?.cast())!)) + ? List.from(map['startAnimations'].map((e) => + AndroidResource.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, toolbarBackgroundColor: map['toolbarBackgroundColor'] != null ? UtilColor.fromStringRepresentation(map['toolbarBackgroundColor']) @@ -296,7 +302,13 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { instance.alwaysUseBrowserUI = map['alwaysUseBrowserUI']; instance.barCollapsingEnabled = map['barCollapsingEnabled']; instance.dismissButtonStyle = - DismissButtonStyle.fromNativeValue(map['dismissButtonStyle']); + switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + DismissButtonStyle.fromNativeValue(map['dismissButtonStyle']), + EnumMethod.value => + DismissButtonStyle.fromValue(map['dismissButtonStyle']), + EnumMethod.name => DismissButtonStyle.byName(map['dismissButtonStyle']) + }; instance.enableUrlBarHiding = map['enableUrlBarHiding']; instance.entersReaderIfAvailable = map['entersReaderIfAvailable']; instance.instantAppsEnabled = map['instantAppsEnabled']; @@ -304,32 +316,57 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { instance.isTrustedWebActivity = map['isTrustedWebActivity']; instance.keepAliveEnabled = map['keepAliveEnabled']; instance.noHistory = map['noHistory']; - instance.presentationStyle = - ModalPresentationStyle.fromNativeValue(map['presentationStyle']); - instance.screenOrientation = + instance.presentationStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalPresentationStyle.fromNativeValue(map['presentationStyle']), + EnumMethod.value => + ModalPresentationStyle.fromValue(map['presentationStyle']), + EnumMethod.name => ModalPresentationStyle.byName(map['presentationStyle']) + }; + instance.screenOrientation = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => TrustedWebActivityScreenOrientation.fromNativeValue( - map['screenOrientation']); - instance.shareState = - CustomTabsShareState.fromNativeValue(map['shareState']); + map['screenOrientation']), + EnumMethod.value => + TrustedWebActivityScreenOrientation.fromValue(map['screenOrientation']), + EnumMethod.name => + TrustedWebActivityScreenOrientation.byName(map['screenOrientation']) + }; + instance.shareState = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CustomTabsShareState.fromNativeValue(map['shareState']), + EnumMethod.value => CustomTabsShareState.fromValue(map['shareState']), + EnumMethod.name => CustomTabsShareState.byName(map['shareState']) + }; instance.showTitle = map['showTitle']; - instance.transitionStyle = - ModalTransitionStyle.fromNativeValue(map['transitionStyle']); + instance.transitionStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalTransitionStyle.fromNativeValue(map['transitionStyle']), + EnumMethod.value => + ModalTransitionStyle.fromValue(map['transitionStyle']), + EnumMethod.name => ModalTransitionStyle.byName(map['transitionStyle']) + }; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "activityButton": activityButton?.toMap(), + "activityButton": activityButton?.toMap(enumMethod: enumMethod), "additionalTrustedOrigins": additionalTrustedOrigins, "alwaysUseBrowserUI": alwaysUseBrowserUI, "barCollapsingEnabled": barCollapsingEnabled, - "dismissButtonStyle": dismissButtonStyle?.toNativeValue(), - "displayMode": displayMode?.toMap(), + "dismissButtonStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => dismissButtonStyle?.toNativeValue(), + EnumMethod.value => dismissButtonStyle?.toValue(), + EnumMethod.name => dismissButtonStyle?.name() + }, + "displayMode": displayMode?.toMap(enumMethod: enumMethod), "enableUrlBarHiding": enableUrlBarHiding, "entersReaderIfAvailable": entersReaderIfAvailable, - "eventAttribution": eventAttribution?.toMap(), - "exitAnimations": exitAnimations?.map((e) => e.toMap()).toList(), + "eventAttribution": eventAttribution?.toMap(enumMethod: enumMethod), + "exitAnimations": + exitAnimations?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "instantAppsEnabled": instantAppsEnabled, "isSingleInstance": isSingleInstance, "isTrustedWebActivity": isTrustedWebActivity, @@ -340,14 +377,31 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { "packageName": packageName, "preferredBarTintColor": preferredBarTintColor?.toHex(), "preferredControlTintColor": preferredControlTintColor?.toHex(), - "presentationStyle": presentationStyle?.toNativeValue(), - "screenOrientation": screenOrientation?.toNativeValue(), + "presentationStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => presentationStyle?.toNativeValue(), + EnumMethod.value => presentationStyle?.toValue(), + EnumMethod.name => presentationStyle?.name() + }, + "screenOrientation": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => screenOrientation?.toNativeValue(), + EnumMethod.value => screenOrientation?.toValue(), + EnumMethod.name => screenOrientation?.name() + }, "secondaryToolbarColor": secondaryToolbarColor?.toHex(), - "shareState": shareState?.toNativeValue(), + "shareState": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => shareState?.toNativeValue(), + EnumMethod.value => shareState?.toValue(), + EnumMethod.name => shareState?.name() + }, "showTitle": showTitle, - "startAnimations": startAnimations?.map((e) => e.toMap()).toList(), + "startAnimations": + startAnimations?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "toolbarBackgroundColor": toolbarBackgroundColor?.toHex(), - "transitionStyle": transitionStyle?.toNativeValue(), + "transitionStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => transitionStyle?.toNativeValue(), + EnumMethod.value => transitionStyle?.toValue(), + EnumMethod.name => transitionStyle?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart b/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart index 6d6a6eb71..5df73b868 100755 --- a/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart +++ b/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart @@ -16,16 +16,22 @@ class ContentBlocker { ContentBlocker({required this.trigger, required this.action}); - Map> toMap() { - return {"trigger": trigger.toMap(), "action": action.toMap()}; + Map> toMap({EnumMethod? enumMethod}) { + return { + "trigger": trigger.toMap(enumMethod: enumMethod), + "action": action.toMap(enumMethod: enumMethod) + }; } - static ContentBlocker fromMap(Map> map) { + static ContentBlocker fromMap(Map> map, + {EnumMethod? enumMethod}) { return ContentBlocker( trigger: ContentBlockerTrigger.fromMap( - Map.from(map["trigger"]!)), + Map.from(map["trigger"]!), + enumMethod: enumMethod), action: ContentBlockerAction.fromMap( - Map.from(map["action"]!))); + Map.from(map["action"]!), + enumMethod: enumMethod)); } @override @@ -138,18 +144,30 @@ class ContentBlockerTrigger { assert(!(this.ifTopUrl.isEmpty || this.unlessTopUrl.isEmpty) == false); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { List resourceTypeStringList = []; resourceType.forEach((type) { - resourceTypeStringList.add(type.toNativeValue()); + resourceTypeStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); List loadTypeStringList = []; loadType.forEach((type) { - loadTypeStringList.add(type.toNativeValue()); + loadTypeStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); List loadContextStringList = []; loadContext.forEach((type) { - loadContextStringList.add(type.toNativeValue()); + loadContextStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); Map map = { @@ -175,7 +193,8 @@ class ContentBlockerTrigger { return map; } - static ContentBlockerTrigger fromMap(Map map) { + static ContentBlockerTrigger fromMap(Map map, + {EnumMethod? enumMethod}) { List resourceType = []; List loadType = []; List loadContext = []; @@ -183,7 +202,13 @@ class ContentBlockerTrigger { List resourceTypeStringList = List.from(map["resource-type"] ?? []); resourceTypeStringList.forEach((typeValue) { - var type = ContentBlockerTriggerResourceType.fromNativeValue(typeValue); + var type = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerResourceType.fromNativeValue(typeValue), + EnumMethod.value => + ContentBlockerTriggerResourceType.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerResourceType.byName(typeValue), + }; if (type != null) { resourceType.add(type); } @@ -191,7 +216,12 @@ class ContentBlockerTrigger { List loadTypeStringList = List.from(map["load-type"] ?? []); loadTypeStringList.forEach((typeValue) { - var type = ContentBlockerTriggerLoadType.fromNativeValue(typeValue); + var type = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerLoadType.fromNativeValue(typeValue), + EnumMethod.value => ContentBlockerTriggerLoadType.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerLoadType.byName(typeValue), + }; if (type != null) { loadType.add(type); } @@ -200,7 +230,13 @@ class ContentBlockerTrigger { List loadContextStringList = List.from(map["load-context"] ?? []); loadContextStringList.forEach((typeValue) { - var context = ContentBlockerTriggerLoadContext.fromNativeValue(typeValue); + var context = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerLoadContext.fromNativeValue(typeValue), + EnumMethod.value => + ContentBlockerTriggerLoadContext.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerLoadContext.byName(typeValue), + }; if (context != null) { loadContext.add(context); } @@ -244,9 +280,13 @@ class ContentBlockerAction { } } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { Map map = { - "type": type.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, "selector": selector }; @@ -260,9 +300,15 @@ class ContentBlockerAction { return map; } - static ContentBlockerAction fromMap(Map map) { + static ContentBlockerAction fromMap(Map map, + {EnumMethod? enumMethod}) { return ContentBlockerAction( - type: ContentBlockerActionType.fromNativeValue(map["type"])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerActionType.fromNativeValue(map["type"]), + EnumMethod.value => ContentBlockerActionType.fromValue(map["type"]), + EnumMethod.name => ContentBlockerActionType.byName(map["type"]) + }!, selector: map["selector"]); } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart index 6e790c4d7..44a3740de 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart @@ -4,6 +4,7 @@ import '../in_app_webview/platform_webview.dart'; import '../types/in_app_webview_hit_test_result.dart'; import 'context_menu_item.dart'; import 'context_menu_settings.dart'; +import '../types/enum_method.dart'; part 'context_menu.g.dart'; @@ -33,7 +34,7 @@ class ContextMenu_ { ///Use [settings] instead @Deprecated("Use settings instead") - final ContextMenuOptions? options; + final ContextMenuOptions_? options; ///Context menu settings. final ContextMenuSettings_? settings; @@ -52,10 +53,11 @@ class ContextMenu_ { @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return { "settings": - (settings as ContextMenuSettings?)?.toMap() ?? options?.toMap() + (settings as ContextMenuSettings?)?.toMap(enumMethod: enumMethod) ?? + (options as ContextMenuOptions?)?.toMap(enumMethod: enumMethod) }; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart index 84e63405f..5bb9e004c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart @@ -52,34 +52,41 @@ class ContextMenu { this.onContextMenuActionItemClicked}); ///Gets a possible [ContextMenu] instance from a [Map] value. - static ContextMenu? fromMap(Map? map) { + static ContextMenu? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ContextMenu( - menuItems: List.from(map['menuItems'] - .map((e) => ContextMenuItem.fromMap(e?.cast())!)), - options: map['settings'], - settings: - ContextMenuSettings.fromMap(map['settings']?.cast()), + menuItems: List.from(map['menuItems'].map((e) => + ContextMenuItem.fromMap(e?.cast(), + enumMethod: enumMethod)!)), + options: ContextMenuOptions.fromMap( + map['settings']?.cast(), + enumMethod: enumMethod), + settings: ContextMenuSettings.fromMap( + map['settings']?.cast(), + enumMethod: enumMethod), ); return instance; } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return { "settings": - (settings as ContextMenuSettings?)?.toMap() ?? options?.toMap() + (settings as ContextMenuSettings?)?.toMap(enumMethod: enumMethod) ?? + (options as ContextMenuOptions?)?.toMap(enumMethod: enumMethod) }; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "menuItems": menuItems.map((e) => e.toMap()).toList(), - "settings": settings?.toMap(), - ..._toMapMergeWith(), + "menuItems": + menuItems.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "settings": settings?.toMap(enumMethod: enumMethod), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart index ec7a29bcd..355b093d4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'context_menu.dart'; import '../util.dart'; +import '../types/enum_method.dart'; part 'context_menu_item.g.dart'; @@ -47,7 +48,7 @@ class ContextMenuItem_ { @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"androidId": androidId, "iosId": iosId}; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart index 242c1f2b5..f40250cda 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart @@ -42,7 +42,8 @@ class ContextMenuItem { } ///Gets a possible [ContextMenuItem] instance from a [Map] value. - static ContextMenuItem? fromMap(Map? map) { + static ContextMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -56,16 +57,16 @@ class ContextMenuItem { } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"androidId": androidId, "iosId": iosId}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, "title": title, - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart index dd6c001a4..f90b3409f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'context_menu.dart'; +import '../types/enum_method.dart'; part 'context_menu_settings.g.dart'; @@ -15,24 +16,10 @@ class ContextMenuSettings_ { ///Use [ContextMenuSettings] instead. @Deprecated("Use ContextMenuSettings instead") -class ContextMenuOptions { +@ExchangeableObject(copyMethod: true) +class ContextMenuOptions_ { ///Whether all the default system context menu items should be hidden or not. The default value is `false`. bool hideDefaultSystemContextMenuItems; - ContextMenuOptions({this.hideDefaultSystemContextMenuItems = false}); - - Map toMap() { - return { - "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } + ContextMenuOptions_({this.hideDefaultSystemContextMenuItems = false}); } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart index 046460726..6713d0a16 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart @@ -13,18 +13,21 @@ class ContextMenuSettings { ContextMenuSettings({this.hideDefaultSystemContextMenuItems = false}); ///Gets a possible [ContextMenuSettings] instance from a [Map] value. - static ContextMenuSettings? fromMap(Map? map) { + static ContextMenuSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ContextMenuSettings(); - instance.hideDefaultSystemContextMenuItems = - map['hideDefaultSystemContextMenuItems']; + if (map['hideDefaultSystemContextMenuItems'] != null) { + instance.hideDefaultSystemContextMenuItems = + map['hideDefaultSystemContextMenuItems']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems, }; @@ -45,3 +48,47 @@ class ContextMenuSettings { return 'ContextMenuSettings{hideDefaultSystemContextMenuItems: $hideDefaultSystemContextMenuItems}'; } } + +///Use [ContextMenuSettings] instead. +@Deprecated('Use ContextMenuSettings instead') +class ContextMenuOptions { + ///Whether all the default system context menu items should be hidden or not. The default value is `false`. + bool hideDefaultSystemContextMenuItems; + ContextMenuOptions({this.hideDefaultSystemContextMenuItems = false}); + + ///Gets a possible [ContextMenuOptions] instance from a [Map] value. + static ContextMenuOptions? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ContextMenuOptions(); + if (map['hideDefaultSystemContextMenuItems'] != null) { + instance.hideDefaultSystemContextMenuItems = + map['hideDefaultSystemContextMenuItems']; + } + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + ///Returns a copy of ContextMenuOptions. + ContextMenuOptions copy() { + return ContextMenuOptions.fromMap(toMap()) ?? ContextMenuOptions(); + } + + @override + String toString() { + return 'ContextMenuOptions{hideDefaultSystemContextMenuItems: $hideDefaultSystemContextMenuItems}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart index 5f082e7ae..b14ab4a09 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart @@ -7,24 +7,25 @@ import '../util.dart'; import 'platform_in_app_browser.dart'; import '../types/main.dart'; +import '../types/enum_method.dart'; part 'in_app_browser_menu_item.g.dart'; -dynamic _serializeIcon(dynamic icon) { - return icon is Uint8List ? icon : icon?.toMap(); +dynamic _serializeIcon(dynamic icon, {EnumMethod? enumMethod}) { + return icon is Uint8List ? icon : icon?.toMap(enumMethod: enumMethod); } -dynamic _deserializeIcon(dynamic icon) { +dynamic _deserializeIcon(dynamic icon, {EnumMethod? enumMethod}) { if (icon is Uint8List) { return icon; } if (icon is Map) { final iconMap = icon as Map; if (iconMap.containsKey('defType')) { - return AndroidResource.fromMap(iconMap); + return AndroidResource.fromMap(iconMap, enumMethod: enumMethod); } if (iconMap.containsKey('systemName')) { - return UIImage.fromMap(iconMap); + return UIImage.fromMap(iconMap, enumMethod: enumMethod); } } return null; diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart index 90620b746..5046e5d82 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart @@ -54,12 +54,13 @@ class InAppBrowserMenuItem { required this.title}); ///Gets a possible [InAppBrowserMenuItem] instance from a [Map] value. - static InAppBrowserMenuItem? fromMap(Map? map) { + static InAppBrowserMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = InAppBrowserMenuItem( - icon: _deserializeIcon(map['icon']), + icon: _deserializeIcon(map['icon'], enumMethod: enumMethod), iconColor: map['iconColor'] != null ? UtilColor.fromStringRepresentation(map['iconColor']) : null, @@ -67,14 +68,16 @@ class InAppBrowserMenuItem { order: map['order'], title: map['title'], ); - instance.showAsAction = map['showAsAction']; + if (map['showAsAction'] != null) { + instance.showAsAction = map['showAsAction']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "icon": _serializeIcon(icon), + "icon": _serializeIcon(icon, enumMethod: enumMethod), "iconColor": iconColor?.toHex(), "id": id, "order": order, diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart index 038f83588..857cce41c 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart @@ -17,6 +17,7 @@ import '../in_app_webview/android/in_app_webview_options.dart'; import 'apple/in_app_browser_options.dart'; import '../in_app_webview/apple/in_app_webview_options.dart'; +import '../types/enum_method.dart'; part 'in_app_browser_settings.g.dart'; @@ -53,14 +54,16 @@ class InAppBrowserClassSettings { } factory InAppBrowserClassSettings.fromMap(Map options, - {InAppBrowserClassSettings? instance}) { + {InAppBrowserClassSettings? instance, EnumMethod? enumMethod}) { if (instance == null) { instance = InAppBrowserClassSettings(); } instance.browserSettings = - InAppBrowserSettings.fromMap(options) ?? InAppBrowserSettings(); + InAppBrowserSettings.fromMap(options, enumMethod: enumMethod) ?? + InAppBrowserSettings(); instance.webViewSettings = - InAppWebViewSettings.fromMap(options) ?? InAppWebViewSettings(); + InAppWebViewSettings.fromMap(options, enumMethod: enumMethod) ?? + InAppWebViewSettings(); return instance; } @@ -98,194 +101,133 @@ class InAppBrowserSettings_ implements BrowserOptions, AndroidOptions, IosOptions { ///Set to `true` to create the browser and load the page, but not show it. Omit or set to `false` to have the browser open and load normally. ///The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) bool? hidden; ///Set to `true` to hide the toolbar at the top of the WebView. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? hideToolbarTop; ///Set the custom background color of the toolbar at the top. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) Color_? toolbarTopBackgroundColor; ///Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? hideUrlBar; ///Set to `true` to hide the progress bar when the WebView is loading a page. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? hideProgressBar; ///Set to `true` to hide the default menu items. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? hideDefaultMenuItems; ///Set to `true` if you want the title should be displayed. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? hideTitleBar; ///Set the action bar's title. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- MacOS + @SupportedPlatforms( + platforms: [AndroidPlatform(), MacOSPlatform(), WindowsPlatform()]) String? toolbarTopFixedTitle; ///Set to `false` to not close the InAppBrowser when the user click on the Android back button and the WebView cannot go back to the history. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? closeOnCannotGoBack; ///Set to `false` to block the InAppBrowser WebView going back when the user click on the Android back button. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? allowGoBackWithBackButton; ///Set to `true` to close the InAppBrowser when the user click on the Android back button. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? shouldCloseOnBackButtonPressed; ///Set to `true` to set the toolbar at the top translucent. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) bool? toolbarTopTranslucent; ///Set the tint color to apply to the navigation bar background. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? toolbarTopBarTintColor; ///Set the tint color to apply to the navigation items and bar button items. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? toolbarTopTintColor; ///Set to `true` to hide the toolbar at the bottom of the WebView. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) bool? hideToolbarBottom; ///Set the custom background color of the toolbar at the bottom. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? toolbarBottomBackgroundColor; ///Set the tint color to apply to the bar button items. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? toolbarBottomTintColor; ///Set to `true` to set the toolbar at the bottom translucent. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) bool? toolbarBottomTranslucent; ///Set the custom text for the close button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) String? closeButtonCaption; ///Set the custom color for the close button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? closeButtonColor; ///Set to `true` to hide the close button. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) bool? hideCloseButton; ///Set the custom color for the menu button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) Color_? menuButtonColor; ///Set the custom modal presentation style when presenting the WebView. The default value is [ModalPresentationStyle.FULL_SCREEN]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) ModalPresentationStyle_? presentationStyle; ///Set to the custom transition style when presenting the WebView. The default value is [ModalTransitionStyle.COVER_VERTICAL]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [IOSPlatform()]) ModalTransitionStyle_? transitionStyle; ///How the browser window should be added to the main window. ///The default value is [WindowType.WINDOW]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [MacOSPlatform(), WindowsPlatform()]) WindowType_? windowType; ///The window’s alpha value. ///The default value is `1.0`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [MacOSPlatform(), WindowsPlatform()]) double? windowAlphaValue; ///Flags that describe the window’s current style, such as if it’s resizable or in full-screen mode. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [MacOSPlatform()]) WindowStyleMask_? windowStyleMask; ///The type of separator that the app displays between the title bar and content of a window. - /// - ///**NOTE for MacOS**: available on MacOS 11.0+. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [MacOSPlatform(available: '11.0')]) WindowTitlebarSeparatorStyle_? windowTitlebarSeparatorStyle; ///Sets the origin and size of the window’s frame rectangle according to a given frame rectangle, ///thereby setting its position and size onscreen. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [MacOSPlatform(), WindowsPlatform()]) InAppWebViewRect_? windowFrame; InAppBrowserSettings_( @@ -390,7 +332,8 @@ class InAppBrowserClassOptions { return toMap().toString(); } - static InAppBrowserClassOptions fromMap(Map options) { + static InAppBrowserClassOptions fromMap(Map options, + {EnumMethod? enumMethod}) { InAppBrowserClassOptions inAppBrowserClassOptions = InAppBrowserClassOptions(); diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart index 87a3b7808..77422cdaa 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart @@ -40,6 +40,7 @@ class InAppBrowserSettings ///- Android native WebView ///- iOS ///- MacOS + ///- Windows bool? hidden; ///Set to `true` to hide the close button. The default value is `false`. @@ -53,6 +54,7 @@ class InAppBrowserSettings ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- MacOS bool? hideDefaultMenuItems; ///Set to `true` to hide the progress bar when the WebView is loading a page. The default value is `false`. @@ -146,6 +148,7 @@ class InAppBrowserSettings ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ///- MacOS + ///- Windows String? toolbarTopFixedTitle; ///Set the tint color to apply to the navigation items and bar button items. @@ -171,6 +174,7 @@ class InAppBrowserSettings /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows double? windowAlphaValue; ///Sets the origin and size of the window’s frame rectangle according to a given frame rectangle, @@ -178,6 +182,7 @@ class InAppBrowserSettings /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows InAppWebViewRect? windowFrame; ///Flags that describe the window’s current style, such as if it’s resizable or in full-screen mode. @@ -188,10 +193,8 @@ class InAppBrowserSettings ///The type of separator that the app displays between the title bar and content of a window. /// - ///**NOTE for MacOS**: available on MacOS 11.0+. - /// ///**Officially Supported Platforms/Implementations**: - ///- MacOS + ///- MacOS 11.0+ WindowTitlebarSeparatorStyle? windowTitlebarSeparatorStyle; ///How the browser window should be added to the main window. @@ -199,6 +202,7 @@ class InAppBrowserSettings /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows WindowType? windowType; InAppBrowserSettings( {this.allowGoBackWithBackButton = true, @@ -231,7 +235,8 @@ class InAppBrowserSettings this.windowType}); ///Gets a possible [InAppBrowserSettings] instance from a [Map] value. - static InAppBrowserSettings? fromMap(Map? map) { + static InAppBrowserSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -257,13 +262,29 @@ class InAppBrowserSettings toolbarTopTintColor: map['toolbarTopTintColor'] != null ? UtilColor.fromStringRepresentation(map['toolbarTopTintColor']) : null, - windowFrame: - InAppWebViewRect.fromMap(map['windowFrame']?.cast()), - windowStyleMask: WindowStyleMask.fromNativeValue(map['windowStyleMask']), - windowTitlebarSeparatorStyle: - WindowTitlebarSeparatorStyle.fromNativeValue( - map['windowTitlebarSeparatorStyle']), - windowType: WindowType.fromNativeValue(map['windowType']), + windowFrame: InAppWebViewRect.fromMap( + map['windowFrame']?.cast(), + enumMethod: enumMethod), + windowStyleMask: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WindowStyleMask.fromNativeValue(map['windowStyleMask']), + EnumMethod.value => WindowStyleMask.fromValue(map['windowStyleMask']), + EnumMethod.name => WindowStyleMask.byName(map['windowStyleMask']) + }, + windowTitlebarSeparatorStyle: switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WindowTitlebarSeparatorStyle.fromNativeValue( + map['windowTitlebarSeparatorStyle']), + EnumMethod.value => WindowTitlebarSeparatorStyle.fromValue( + map['windowTitlebarSeparatorStyle']), + EnumMethod.name => WindowTitlebarSeparatorStyle.byName( + map['windowTitlebarSeparatorStyle']) + }, + windowType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WindowType.fromNativeValue(map['windowType']), + EnumMethod.value => WindowType.fromValue(map['windowType']), + EnumMethod.name => WindowType.byName(map['windowType']) + }, ); instance.allowGoBackWithBackButton = map['allowGoBackWithBackButton']; instance.closeOnCannotGoBack = map['closeOnCannotGoBack']; @@ -275,8 +296,13 @@ class InAppBrowserSettings instance.hideToolbarBottom = map['hideToolbarBottom']; instance.hideToolbarTop = map['hideToolbarTop']; instance.hideUrlBar = map['hideUrlBar']; - instance.presentationStyle = - ModalPresentationStyle.fromNativeValue(map['presentationStyle']); + instance.presentationStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalPresentationStyle.fromNativeValue(map['presentationStyle']), + EnumMethod.value => + ModalPresentationStyle.fromValue(map['presentationStyle']), + EnumMethod.name => ModalPresentationStyle.byName(map['presentationStyle']) + }; instance.shouldCloseOnBackButtonPressed = map['shouldCloseOnBackButtonPressed']; instance.toolbarBottomTranslucent = map['toolbarBottomTranslucent']; @@ -284,14 +310,19 @@ class InAppBrowserSettings ? UtilColor.fromStringRepresentation(map['toolbarTopBarTintColor']) : null; instance.toolbarTopTranslucent = map['toolbarTopTranslucent']; - instance.transitionStyle = - ModalTransitionStyle.fromNativeValue(map['transitionStyle']); + instance.transitionStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalTransitionStyle.fromNativeValue(map['transitionStyle']), + EnumMethod.value => + ModalTransitionStyle.fromValue(map['transitionStyle']), + EnumMethod.name => ModalTransitionStyle.byName(map['transitionStyle']) + }; instance.windowAlphaValue = map['windowAlphaValue']; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowGoBackWithBackButton": allowGoBackWithBackButton, "closeButtonCaption": closeButtonCaption, @@ -306,7 +337,11 @@ class InAppBrowserSettings "hideToolbarTop": hideToolbarTop, "hideUrlBar": hideUrlBar, "menuButtonColor": menuButtonColor?.toHex(), - "presentationStyle": presentationStyle?.toNativeValue(), + "presentationStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => presentationStyle?.toNativeValue(), + EnumMethod.value => presentationStyle?.toValue(), + EnumMethod.name => presentationStyle?.name() + }, "shouldCloseOnBackButtonPressed": shouldCloseOnBackButtonPressed, "toolbarBottomBackgroundColor": toolbarBottomBackgroundColor?.toHex(), "toolbarBottomTintColor": toolbarBottomTintColor?.toHex(), @@ -316,13 +351,29 @@ class InAppBrowserSettings "toolbarTopFixedTitle": toolbarTopFixedTitle, "toolbarTopTintColor": toolbarTopTintColor?.toHex(), "toolbarTopTranslucent": toolbarTopTranslucent, - "transitionStyle": transitionStyle?.toNativeValue(), + "transitionStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => transitionStyle?.toNativeValue(), + EnumMethod.value => transitionStyle?.toValue(), + EnumMethod.name => transitionStyle?.name() + }, "windowAlphaValue": windowAlphaValue, - "windowFrame": windowFrame?.toMap(), - "windowStyleMask": windowStyleMask?.toNativeValue(), - "windowTitlebarSeparatorStyle": - windowTitlebarSeparatorStyle?.toNativeValue(), - "windowType": windowType?.toNativeValue(), + "windowFrame": windowFrame?.toMap(enumMethod: enumMethod), + "windowStyleMask": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowStyleMask?.toNativeValue(), + EnumMethod.value => windowStyleMask?.toValue(), + EnumMethod.name => windowStyleMask?.name() + }, + "windowTitlebarSeparatorStyle": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowTitlebarSeparatorStyle?.toNativeValue(), + EnumMethod.value => windowTitlebarSeparatorStyle?.toValue(), + EnumMethod.name => windowTitlebarSeparatorStyle?.name() + }, + "windowType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowType?.toNativeValue(), + EnumMethod.value => windowType?.toValue(), + EnumMethod.name => windowType?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart index 999b92a3b..a22ae1e3b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart @@ -1,28 +1,25 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview_platform_interface/src/types/disposable.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../context_menu/context_menu.dart'; +import '../debug_logging_settings.dart'; import '../find_interaction/platform_find_interaction_controller.dart'; +import '../in_app_webview/in_app_webview_settings.dart'; +import '../in_app_webview/platform_inappwebview_controller.dart'; import '../inappwebview_platform.dart'; +import '../platform_webview_feature.dart'; +import '../print_job/main.dart'; import '../pull_to_refresh/main.dart'; +import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; import '../types/main.dart'; - -import '../in_app_webview/platform_inappwebview_controller.dart'; -import '../in_app_webview/in_app_webview_settings.dart'; - -import '../print_job/main.dart'; import '../web_uri.dart'; +import '../webview_environment/platform_webview_environment.dart'; import 'in_app_browser_menu_item.dart'; import 'in_app_browser_settings.dart'; -import '../debug_logging_settings.dart'; -import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppBrowser]. /// @@ -31,12 +28,14 @@ import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; @immutable class PlatformInAppBrowserCreationParams { /// Used by the platform implementation to create a new [PlatformInAppBrowser]. - const PlatformInAppBrowserCreationParams( - {this.contextMenu, - this.pullToRefreshController, - this.findInteractionController, - this.initialUserScripts, - this.windowId}); + const PlatformInAppBrowserCreationParams({ + this.contextMenu, + this.pullToRefreshController, + this.findInteractionController, + this.initialUserScripts, + this.windowId, + this.webViewEnvironment, + }); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser.contextMenu} final ContextMenu? contextMenu; @@ -52,6 +51,12 @@ class PlatformInAppBrowserCreationParams { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser.windowId} final int? windowId; + + ///Used to create the [PlatformInAppBrowser] using the specified environment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + final PlatformWebViewEnvironment? webViewEnvironment; } ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser} @@ -64,6 +69,7 @@ class PlatformInAppBrowserCreationParams { ///- Android native WebView ///- iOS ///- MacOS +///- Windows ///{@endtemplate} abstract class PlatformInAppBrowser extends PlatformInterface implements Disposable { @@ -82,29 +88,53 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.contextMenu} ///Context menu used by the browser. It should be set before opening the browser. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS ///{@endtemplate} ContextMenu? get contextMenu => params.contextMenu; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.pullToRefreshController} ///Represents the pull-to-refresh feature controller. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS ///{@endtemplate} PlatformPullToRefreshController? get pullToRefreshController => params.pullToRefreshController; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.findInteractionController} ///Represents the find interaction feature controller. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS ///{@endtemplate} PlatformFindInteractionController? get findInteractionController => params.findInteractionController; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.initialUserScripts} ///Initial list of user scripts to be loaded at start or end of a page loading. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows ///{@endtemplate} UnmodifiableListView? get initialUserScripts => params.initialUserScripts; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.windowId} ///The window id of a [CreateWindowAction.windowId]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS ///{@endtemplate} int? get windowId => params.windowId; @@ -172,6 +202,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openUrlRequest( {required URLRequest urlRequest, @@ -225,6 +256,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openFile( {required String assetFilePath, @@ -252,6 +284,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openData( {required String data, @@ -274,6 +307,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openWithSystemBrowser({required WebUri url}) { throw UnimplementedError( @@ -288,6 +322,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} void addMenuItem(InAppBrowserMenuItem menuItem) { throw UnimplementedError( @@ -302,6 +337,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} void addMenuItems(List menuItems) { throw UnimplementedError( @@ -317,6 +353,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} bool removeMenuItem(InAppBrowserMenuItem menuItem) { throw UnimplementedError( @@ -331,6 +368,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} void removeMenuItems(List menuItems) { throw UnimplementedError( @@ -345,6 +383,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} void removeAllMenuItem() { throw UnimplementedError( @@ -357,6 +396,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ///- iOS 14.0+ + ///- macOS 10.15+ ///{@endtemplate} bool hasMenuItem(InAppBrowserMenuItem menuItem) { throw UnimplementedError( @@ -370,6 +410,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future show() { throw UnimplementedError('show is not implemented on the current platform'); @@ -382,6 +423,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future hide() { throw UnimplementedError('hide is not implemented on the current platform'); @@ -394,6 +436,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future close() { throw UnimplementedError( @@ -407,6 +450,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future isHidden() { throw UnimplementedError( @@ -464,6 +508,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} bool isOpened() { throw UnimplementedError( @@ -487,6 +532,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows void onBrowserCreated() {} ///Event fired when the [PlatformInAppBrowser] window is closed. @@ -495,6 +541,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows void onExit() {} ///Event fired when the main window is about to close. @@ -509,6 +556,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onPageStarted](https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationStarting](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationstarting)) void onLoadStart(WebUri? url) {} ///Event fired when the [PlatformInAppBrowser] finishes loading an [url]. @@ -517,6 +565,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onPageFinished](https://developer.android.com/reference/android/webkit/WebViewClient#onPageFinished(android.webkit.WebView,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onLoadStop(WebUri? url) {} ///Use [onReceivedError] instead. @@ -529,6 +578,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceError))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onReceivedError(WebResourceRequest request, WebResourceError error) {} ///Use [onReceivedHttpError] instead. @@ -547,6 +597,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedHttpError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceResponse))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onReceivedHttpError( WebResourceRequest request, WebResourceResponse errorResponse) {} @@ -556,6 +607,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onProgressChanged](https://developer.android.com/reference/android/webkit/WebChromeClient#onProgressChanged(android.webkit.WebView,%20int))) ///- iOS ///- MacOS + ///- Windows void onProgressChanged(int progress) {} ///Event fired when the [PlatformInAppBrowser] webview receives a [ConsoleMessage]. @@ -564,6 +616,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onConsoleMessage](https://developer.android.com/reference/android/webkit/WebChromeClient#onConsoleMessage(android.webkit.ConsoleMessage))) ///- iOS ///- MacOS + ///- Windows void onConsoleMessage(ConsoleMessage consoleMessage) {} ///Give the host application a chance to take control when a URL is about to be loaded in the current WebView. This event is not called on the initial load of the WebView. @@ -582,7 +635,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.shouldOverrideUrlLoading](https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview)) - Future? shouldOverrideUrlLoading( + FutureOr? shouldOverrideUrlLoading( NavigationAction navigationAction) { return null; } @@ -611,10 +664,14 @@ abstract class PlatformInAppBrowserEvents { ///- MacOS void onScrollChanged(int x, int y) {} - ///Use [onDownloadStartRequest] instead - @Deprecated('Use onDownloadStartRequest instead') + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') void onDownloadStart(Uri url) {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') + void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + ///Event fired when `WebView` recognizes a downloadable file. ///To download the file, you can use the [flutter_downloader](https://pub.dev/packages/flutter_downloader) plugin. /// @@ -626,11 +683,15 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebView.setDownloadListener](https://developer.android.com/reference/android/webkit/WebView#setDownloadListener(android.webkit.DownloadListener))) ///- iOS ///- MacOS - void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + ///- Windows ([Official API - ICoreWebView2_4.add_DownloadStarting](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_4?view=webview2-1.0.2849.39#add_downloadstarting)) + FutureOr? onDownloadStarting( + DownloadStartRequest downloadStartRequest) { + return null; + } ///Use [onLoadResourceWithCustomScheme] instead. @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future? onLoadResourceCustomScheme(Uri url) { + FutureOr? onLoadResourceCustomScheme(Uri url) { return null; } @@ -641,7 +702,8 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ([Official API - WKURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkurlschemehandler)) ///- MacOS ([Official API - WKURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkurlschemehandler)) - Future? onLoadResourceWithCustomScheme( + ///- Windows + FutureOr? onLoadResourceWithCustomScheme( WebResourceRequest request) { return null; } @@ -678,7 +740,8 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onCreateWindow](https://developer.android.com/reference/android/webkit/WebChromeClient#onCreateWindow(android.webkit.WebView,%20boolean,%20boolean,%20android.os.Message))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview)) - Future? onCreateWindow(CreateWindowAction createWindowAction) { + ///- Windows ([Official API - ICoreWebView2.add_NewWindowRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_newwindowrequested)) + FutureOr? onCreateWindow(CreateWindowAction createWindowAction) { return null; } @@ -689,6 +752,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onCloseWindow](https://developer.android.com/reference/android/webkit/WebChromeClient#onCloseWindow(android.webkit.WebView))) ///- iOS ([Official API - WKUIDelegate.webViewDidClose](https://developer.apple.com/documentation/webkit/wkuidelegate/1537390-webviewdidclose)) ///- MacOS ([Official API - WKUIDelegate.webViewDidClose](https://developer.apple.com/documentation/webkit/wkuidelegate/1537390-webviewdidclose)) + ///- Windows ([Official API - ICoreWebView2.add_WindowCloseRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_windowcloserequested)) void onCloseWindow() {} ///Event fired when the JavaScript `window` object of the WebView has received focus. @@ -718,7 +782,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsAlert](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsAlert(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview)) - Future? onJsAlert(JsAlertRequest jsAlertRequest) { + FutureOr? onJsAlert(JsAlertRequest jsAlertRequest) { return null; } @@ -731,7 +795,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsConfirm](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsConfirm(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview)) - Future? onJsConfirm(JsConfirmRequest jsConfirmRequest) { + FutureOr? onJsConfirm(JsConfirmRequest jsConfirmRequest) { return null; } @@ -744,7 +808,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsPrompt](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsPrompt(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20android.webkit.JsPromptResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview)) - Future? onJsPrompt(JsPromptRequest jsPromptRequest) { + FutureOr? onJsPrompt(JsPromptRequest jsPromptRequest) { return null; } @@ -756,8 +820,9 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedHttpAuthRequest](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView,%20android.webkit.HttpAuthHandler,%20java.lang.String,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedHttpAuthRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_10.add_BasicAuthenticationRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_10?view=webview2-1.0.2849.39#add_basicauthenticationrequested)) + FutureOr? onReceivedHttpAuthRequest( + HttpAuthenticationChallenge challenge) { return null; } @@ -766,12 +831,17 @@ abstract class PlatformInAppBrowserEvents { /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge]. /// + ///**NOTE for iOS and macOS**: to override the certificate verification logic, you have to provide ATS (App Transport Security) exceptions in your iOS/macOS `Info.plist`. + ///See `NSAppTransportSecurity` in the [Information Property List Key Reference](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW1) + ///for details. + /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onReceivedSslError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%20android.webkit.SslErrorHandler,%20android.net.http.SslError))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedServerTrustAuthRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_14.add_ServerCertificateErrorDetected](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_14?view=webview2-1.0.2792.45#add_servercertificateerrordetected)) + FutureOr? onReceivedServerTrustAuthRequest( + ServerTrustChallenge challenge) { return null; } @@ -786,8 +856,9 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedClientCertRequest](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedClientCertRequest(android.webkit.WebView,%20android.webkit.ClientCertRequest))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedClientCertRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_5.add_ClientCertificateRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_5?view=webview2-1.0.2849.39#add_clientcertificaterequested)) + FutureOr? onReceivedClientCertRequest( + ClientCertChallenge challenge) { return null; } @@ -798,11 +869,17 @@ abstract class PlatformInAppBrowserEvents { ///Event fired when an `XMLHttpRequest` is sent to a server. ///It gives the host application a chance to take control over the request before sending it. + ///This event is implemented using JavaScript under the hood. + /// + ///Due to the async nature of this event implementation, it will intercept only async `XMLHttpRequest`s ([AjaxRequest.isAsync] with `true`). + ///To be able to intercept sync `XMLHttpRequest`s, use [InAppWebViewSettings.interceptOnlyAsyncAjaxRequests] to `false`. + ///If necessary, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// ///[ajaxRequest] represents the `XMLHttpRequest`. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. @@ -811,18 +888,24 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { + FutureOr? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { return null; } ///Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes. ///It gives the host application a chance to abort the request. + ///This event is implemented using JavaScript under the hood. + /// + ///Due to the async nature of this event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// ///[ajaxRequest] represents the [XMLHttpRequest]. /// - ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that - ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] and [InAppWebViewSettings.useOnAjaxReadyStateChange] settings to `true`. + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. /// @@ -830,17 +913,19 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onAjaxReadyStateChange(AjaxRequest ajaxRequest) { + FutureOr? onAjaxReadyStateChange( + AjaxRequest ajaxRequest) { return null; } ///Event fired as an `XMLHttpRequest` progress. ///It gives the host application a chance to abort the request. + ///This event is implemented using JavaScript under the hood. /// ///[ajaxRequest] represents the [XMLHttpRequest]. /// - ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] and [InAppWebViewSettings.useOnAjaxProgress] settings to `true`. + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. @@ -849,17 +934,18 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onAjaxProgress(AjaxRequest ajaxRequest) { + FutureOr? onAjaxProgress(AjaxRequest ajaxRequest) { return null; } ///Event fired when a request is sent to a server through [Fetch API](https://developer.mozilla.org/it/docs/Web/API/Fetch_API). ///It gives the host application a chance to take control over the request before sending it. + ///This event is implemented using JavaScript under the hood. /// ///[fetchRequest] represents a resource request. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptFetchRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept fetch requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the fetch requests will be intercept for sure. @@ -868,7 +954,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? shouldInterceptFetchRequest( + FutureOr? shouldInterceptFetchRequest( FetchRequest fetchRequest) { return null; } @@ -886,6 +972,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.doUpdateVisitedHistory](https://developer.android.com/reference/android/webkit/WebViewClient#doUpdateVisitedHistory(android.webkit.WebView,%20java.lang.String,%20boolean))) ///- iOS ///- MacOS + ///- Windows ([Official API - ICoreWebView2.add_HistoryChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_historychanged)) void onUpdateVisitedHistory(WebUri? url, bool? isReload) {} ///Use [onPrintRequest] instead @@ -904,7 +991,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onPrintRequest( + FutureOr? onPrintRequest( WebUri? url, PlatformPrintJobController? printJobController) { return null; } @@ -955,6 +1042,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onReceivedTitle](https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String))) ///- iOS ///- MacOS + ///- Windows ([Official API - ICoreWebView2.add_DocumentTitleChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_documenttitlechanged)) void onTitleChanged(String? title) {} ///Event fired to respond to the results of an over-scroll operation. @@ -981,11 +1069,12 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onScaleChanged](https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float))) ///- iOS ([Official API - UIScrollViewDelegate.scrollViewDidZoom](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619409-scrollviewdidzoom)) + ///- Windows ([Official API - ICoreWebView2Controller.add_ZoomFactorChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller?view=webview2-1.0.2849.39#add_zoomfactorchanged)) void onZoomScaleChanged(double oldScale, double newScale) {} ///Use [onSafeBrowsingHit] instead. @Deprecated("Use onSafeBrowsingHit instead") - Future? androidOnSafeBrowsingHit( + FutureOr? androidOnSafeBrowsingHit( Uri url, SafeBrowsingThreat? threatType) { return null; } @@ -1001,14 +1090,14 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onSafeBrowsingHit](https://developer.android.com/reference/android/webkit/WebViewClient#onSafeBrowsingHit(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20int,%20android.webkit.SafeBrowsingResponse))) - Future? onSafeBrowsingHit( + FutureOr? onSafeBrowsingHit( WebUri url, SafeBrowsingThreat? threatType) { return null; } ///Use [onPermissionRequest] instead. @Deprecated("Use onPermissionRequest instead") - Future? androidOnPermissionRequest( + FutureOr? androidOnPermissionRequest( String origin, List resources) { return null; } @@ -1028,14 +1117,15 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onPermissionRequest](https://developer.android.com/reference/android/webkit/WebChromeClient#onPermissionRequest(android.webkit.PermissionRequest))) ///- iOS ///- MacOS - Future? onPermissionRequest( + ///- Windows ([Official API - ICoreWebView2.add_PermissionRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_permissionrequested)) + FutureOr? onPermissionRequest( PermissionRequest permissionRequest) { return null; } ///Use [onGeolocationPermissionsShowPrompt] instead. @Deprecated("Use onGeolocationPermissionsShowPrompt instead") - Future? + FutureOr? androidOnGeolocationPermissionsShowPrompt(String origin) { return null; } @@ -1048,7 +1138,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onGeolocationPermissionsShowPrompt](https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback))) - Future? + FutureOr? onGeolocationPermissionsShowPrompt(String origin) { return null; } @@ -1066,7 +1156,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [shouldInterceptRequest] instead. @Deprecated("Use shouldInterceptRequest instead") - Future? androidShouldInterceptRequest( + FutureOr? androidShouldInterceptRequest( WebResourceRequest request) { return null; } @@ -1087,14 +1177,15 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.shouldInterceptRequest](https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest))) - Future? shouldInterceptRequest( + ///- Windows ([ICoreWebView2.add_WebResourceRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2478.35#add_webresourcerequested)) + FutureOr? shouldInterceptRequest( WebResourceRequest request) { return null; } ///Use [onRenderProcessUnresponsive] instead. @Deprecated("Use onRenderProcessUnresponsive instead") - Future? androidOnRenderProcessUnresponsive( + FutureOr? androidOnRenderProcessUnresponsive( Uri? url) { return null; } @@ -1118,14 +1209,15 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewRenderProcessClient.onRenderProcessUnresponsive](https://developer.android.com/reference/android/webkit/WebViewRenderProcessClient#onRenderProcessUnresponsive(android.webkit.WebView,%20android.webkit.WebViewRenderProcess))) - Future? onRenderProcessUnresponsive( + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) + FutureOr? onRenderProcessUnresponsive( WebUri? url) { return null; } ///Use [onRenderProcessResponsive] instead. @Deprecated("Use onRenderProcessResponsive instead") - Future? androidOnRenderProcessResponsive( + FutureOr? androidOnRenderProcessResponsive( Uri? url) { return null; } @@ -1142,7 +1234,8 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewRenderProcessClient.onRenderProcessResponsive](https://developer.android.com/reference/android/webkit/WebViewRenderProcessClient#onRenderProcessResponsive(android.webkit.WebView,%20android.webkit.WebViewRenderProcess))) - Future? onRenderProcessResponsive(WebUri? url) { + FutureOr? onRenderProcessResponsive( + WebUri? url) { return null; } @@ -1154,17 +1247,21 @@ abstract class PlatformInAppBrowserEvents { ///The application's implementation of this callback should only attempt to clean up the WebView. ///The WebView should be removed from the view hierarchy, all references to it should be cleaned up. /// + ///To cause an render process crash for test purpose, the application can call load url `"chrome://crash"` on the WebView. + ///Note that multiple WebView instances may be affected if they share a render process, not just the specific WebView which loaded `"chrome://crash"`. + /// ///[detail] the reason why it exited. /// ///**NOTE**: available only on Android 26+. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onRenderProcessGone](https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail))) + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) void onRenderProcessGone(RenderProcessGoneDetail detail) {} ///Use [onFormResubmission] instead. @Deprecated('Use onFormResubmission instead') - Future? androidOnFormResubmission(Uri? url) { + FutureOr? androidOnFormResubmission(Uri? url) { return null; } @@ -1172,7 +1269,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onFormResubmission](https://developer.android.com/reference/android/webkit/WebViewClient#onFormResubmission(android.webkit.WebView,%20android.os.Message,%20android.os.Message))) - Future? onFormResubmission(WebUri? url) { + FutureOr? onFormResubmission(WebUri? url) { return null; } @@ -1208,7 +1305,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [onJsBeforeUnload] instead. @Deprecated('Use onJsBeforeUnload instead') - Future? androidOnJsBeforeUnload( + FutureOr? androidOnJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @@ -1225,7 +1322,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onJsBeforeUnload](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsBeforeUnload(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) - Future? onJsBeforeUnload( + FutureOr? onJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @@ -1266,10 +1363,12 @@ abstract class PlatformInAppBrowserEvents { void iosOnWebContentProcessDidTerminate() {} ///Invoked when the web view's web content process is terminated. + ///Reloading the page will start a new render process if needed. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webViewWebContentProcessDidTerminate](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi)) ///- MacOS ([Official API - WKNavigationDelegate.webViewWebContentProcessDidTerminate](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi)) + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) void onWebContentProcessDidTerminate() {} ///Use [onDidReceiveServerRedirectForProvisionalNavigation] instead. @@ -1285,7 +1384,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [onNavigationResponse] instead. @Deprecated('Use onNavigationResponse instead') - Future? iosOnNavigationResponse( + FutureOr? iosOnNavigationResponse( IOSWKNavigationResponse navigationResponse) { return null; } @@ -1299,14 +1398,14 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) - Future? onNavigationResponse( + FutureOr? onNavigationResponse( NavigationResponse navigationResponse) { return null; } ///Use [shouldAllowDeprecatedTLS] instead. @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future? iosShouldAllowDeprecatedTLS( + FutureOr? iosShouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @@ -1322,7 +1421,7 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/3601237-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/3601237-webview)) - Future? shouldAllowDeprecatedTLS( + FutureOr? shouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @@ -1365,4 +1464,48 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} + + ///Invoked when any of the processes in the WebView Process Group encounters one of the following conditions: + ///- Unexpected exit: The process indicated by the event args has exited unexpectedly (usually due to a crash). + ///The failure might or might not be recoverable and some failures are auto-recoverable. + ///- Unresponsiveness: The process indicated by the event args has become unresponsive to user input. + ///This is only reported for renderer processes, and will run every few seconds until the process becomes responsive again. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) + void onProcessFailed(ProcessFailedDetail detail) {} + + ///This event runs when an accelerator key or key combo is pressed or + ///released while the WebView is focused. + ///A key is considered an accelerator if either of the following conditions are `true`: + ///- `Ctrl` or `Alt` is currently being held. + ///- The pressed key does not map to a character. + /// + ///A few specific keys are never considered accelerators, such as `Shift`. + ///The `Escape` key is always considered an accelerator. + /// + ///Auto-repeated key events caused by holding the key down also triggers this event. + ///Filter out the auto-repeated key events by verifying the [AcceleratorKeyPressedDetail.physicalKeyStatus] property. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2Controller.add_AcceleratorKeyPressed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller?view=webview2-1.0.2849.39#add_acceleratorkeypressed)) + void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + ///Tell the client to show a file chooser. + ///This is called to handle HTML forms with 'file' input type, + ///in response to the user pressing the "Select File" button. + ///To cancel the request, return a [ShowFileChooserResponse] with [ShowFileChooserResponse.filePaths] to `null`. + /// + ///Note that the WebView does not enforce any restrictions on the chosen file(s). + ///WebView can access all files that your app can access. + ///In case the file(s) are chosen through an untrusted source such as a third-party app, + ///it is your own app's responsibility to check what the returned Uris refer + ///to. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - WebChromeClient.onShowFileChooser](https://developer.android.com/reference/android/webkit/WebChromeClient#onShowFileChooser(android.webkit.WebView,%20android.webkit.ValueCallback%3Candroid.net.Uri[]%3E,%20android.webkit.WebChromeClient.FileChooserParams))) + FutureOr onShowFileChooser( + ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart index f1805c109..3cd048b90 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart @@ -38,6 +38,7 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { bool _shared = false; String _directoryIndex = 'index.html'; String _documentRoot = './'; + Future Function(HttpRequest)? _customOnData; /// Creates a new [DefaultInAppLocalhostServer]. DefaultInAppLocalhostServer(PlatformInAppLocalhostServerCreationParams params) @@ -53,6 +54,7 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { ? params.documentRoot : '${params.documentRoot}/'; this._shared = params.shared; + this._customOnData = params.onData; } @override @@ -67,6 +69,9 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { @override bool get shared => _shared; + @override + Future Function(HttpRequest request)? get onData => _customOnData; + @override Future start() async { if (this._started) { @@ -78,11 +83,19 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { runZonedGuarded(() { HttpServer.bind('127.0.0.1', _port, shared: _shared).then((server) { - print('Server running on http://localhost:' + _port.toString()); + if (kDebugMode) { + print('Server running on http://localhost:' + _port.toString()); + } this._server = server; server.listen((HttpRequest request) async { + if (await _customOnData?.call(request) ?? false) { + // if _customOnData returns true, + // it means that the request has been handled + return; + } + Uint8List body = Uint8List(0); var path = request.requestedUri.path; @@ -99,8 +112,10 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { .buffer .asUint8List(); } catch (e) { - print(Uri.decodeFull(path)); - print(e.toString()); + if (kDebugMode) { + print(Uri.decodeFull(path)); + print(e.toString()); + } request.response.close(); return; } @@ -115,13 +130,18 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { } request.response.headers.contentType = contentType; + print(request.response.headers); request.response.add(body); request.response.close(); }); completer.complete(); }); - }, (e, stackTrace) => print('Error: $e $stackTrace')); + }, (e, stackTrace) { + if (kDebugMode) { + print('Error: $e $stackTrace'); + } + }); return completer.future; } @@ -132,7 +152,9 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { return; } await this._server!.close(force: true); - print('Server running on http://localhost:$_port closed'); + if (kDebugMode) { + print('Server running on http://localhost:$_port closed'); + } this._started = false; this._server = null; } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart index e026022fb..cd7cff328 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart @@ -22,18 +22,20 @@ extension InternalInAppWebViewKeepAlive on InAppWebViewKeepAlive { ///Used internally to save and restore [PlatformInAppWebViewController] properties ///for the keep alive feature. class InAppWebViewControllerKeepAliveProps { - Map javaScriptHandlersMap; + Map javaScriptHandlersMap; Map> userScripts; Set webMessageListenerObjNames; Map injectedScriptsFromURL; Set webMessageChannels = Set(); Set webMessageListeners = Set(); + Map devToolsProtocolEventListenerMap; InAppWebViewControllerKeepAliveProps( - {required this.javaScriptHandlersMap, - required this.userScripts, - required this.webMessageListenerObjNames, - required this.injectedScriptsFromURL, - required this.webMessageChannels, - required this.webMessageListeners}); + {this.javaScriptHandlersMap = const {}, + this.userScripts = const {}, + this.webMessageListenerObjNames = const {}, + this.injectedScriptsFromURL = const {}, + this.webMessageChannels = const {}, + this.webMessageListeners = const {}, + this.devToolsProtocolEventListenerMap = const {}}); } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart index a53edcc25..2fa1c655b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart @@ -1,9 +1,10 @@ +import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; -import 'dart:typed_data'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../platform_webview_asset_loader.dart'; import '../types/action_mode_menu_item.dart'; import '../types/cache_mode.dart'; import '../types/data_detector_types.dart'; @@ -12,6 +13,7 @@ import '../types/force_dark_strategy.dart'; import '../types/layout_algorithm.dart'; import '../types/mixed_content_mode.dart'; import '../types/over_scroll_mode.dart'; +import '../types/pdf_toolbar_items.dart'; import '../types/referrer_policy.dart'; import '../types/renderer_priority_policy.dart'; import '../types/sandbox.dart'; @@ -21,29 +23,19 @@ import '../types/scrollview_deceleration_rate.dart'; import '../types/selection_granularity.dart'; import '../types/user_preferred_content_mode.dart'; import '../types/vertical_scrollbar_position.dart'; -import '../web_uri.dart'; -import 'android/in_app_webview_options.dart'; -import 'apple/in_app_webview_options.dart'; -import '../content_blocker.dart'; -import '../types/main.dart'; -import '../util.dart'; -import '../in_app_browser/in_app_browser_settings.dart'; -import '../platform_webview_feature.dart'; -import '../in_app_webview/platform_inappwebview_controller.dart'; -import '../context_menu/context_menu.dart'; -import '../in_app_browser/platform_in_app_browser.dart'; -import 'platform_webview.dart'; part 'in_app_webview_settings.g.dart'; List _deserializeContentBlockers( - List? contentBlockersMapList) { + List? contentBlockersMapList, + {EnumMethod? enumMethod}) { List contentBlockers = []; if (contentBlockersMapList != null) { contentBlockersMapList.forEach((contentBlocker) { contentBlockers.add(ContentBlocker.fromMap( Map>.from( - Map.from(contentBlocker)))); + Map.from(contentBlocker)), + enumMethod: enumMethod)); }); } return contentBlockers; @@ -57,8 +49,12 @@ class InAppWebViewSettings_ { ///If the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event is implemented and this value is `null`, ///it will be automatically inferred as `true`, otherwise, the default value is `false`. ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. - @SupportedPlatforms( - platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) bool? useShouldOverrideUrlLoading; ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onLoadResource] event. @@ -81,6 +77,7 @@ class InAppWebViewSettings_ { ///Use [PlatformInAppWebViewController.clearAllCache] instead. @Deprecated("Use InAppWebViewController.clearAllCache instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms( platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? clearCache; @@ -98,7 +95,11 @@ class InAppWebViewSettings_ { MacOSPlatform( apiName: "WKWebView.customUserAgent", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent") + "https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent"), + WindowsPlatform( + apiName: 'ICoreWebView2Settings2.put_UserAgent', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings2?view=webview2-1.0.2210.55#put_useragent') ]) String? userAgent; @@ -130,7 +131,11 @@ class InAppWebViewSettings_ { apiName: "WKWebpagePreferences.allowsContentJavaScript", apiUrl: "https://developer.apple.com/documentation/webkit/wkwebpagepreferences/3552422-allowscontentjavascript/"), - WebPlatform(requiresSameOrigin: false) + WebPlatform(requiresSameOrigin: false), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsScriptEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_isscriptenabled") ]) bool? javaScriptEnabled; @@ -259,15 +264,46 @@ class InAppWebViewSettings_ { ///Due to the async nature of [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event implementation, ///it will intercept only async `XMLHttpRequest`s ([AjaxRequest.isAsync] with `true`). ///To be able to intercept sync `XMLHttpRequest`s, use [InAppWebViewSettings.interceptOnlyAsyncAjaxRequests] to `false`. + ///If necessary, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// - ///If the [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event or - ///any other Ajax event is implemented and this value is `null`, + ///If the [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event is implemented and this value is `null`, ///it will be automatically inferred as `true`, otherwise, the default value is `false`. ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. @SupportedPlatforms( platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? useShouldInterceptAjaxRequest; + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onAjaxReadyStateChange] event. + ///Also, [useShouldInterceptAjaxRequest] must be set to `true` to take effect. + /// + ///Due to the async nature of [PlatformWebViewCreationParams.onAjaxReadyStateChange] event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. + /// + ///If the [PlatformWebViewCreationParams.onAjaxReadyStateChange] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + bool? useOnAjaxReadyStateChange; + + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onAjaxProgress] event. + ///Also, [useShouldInterceptAjaxRequest] must be set to `true` to take effect. + /// + ///Due to the async nature of [PlatformWebViewCreationParams.onAjaxProgress] event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. + /// + ///If the [PlatformWebViewCreationParams.onAjaxProgress] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + bool? useOnAjaxProgress; + ///Set to `false` to be able to listen to also sync `XMLHttpRequest`s at the ///[PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event. /// @@ -294,7 +330,11 @@ class InAppWebViewSettings_ { """setting this to `true`, it will clear all the cookies of all WebView instances, because there isn't any way to make the website data store non-persistent for the specific WebView instance such as on iOS."""), IOSPlatform(), - MacOSPlatform() + MacOSPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2ControllerOptions.put_IsInPrivateModeEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controlleroptions?view=webview2-1.0.2792.45#put_isinprivatemodeenabled") ]) bool? incognito; @@ -307,7 +347,12 @@ because there isn't any way to make the website data store non-persistent for th @SupportedPlatforms(platforms: [ AndroidPlatform(), IOSPlatform(), - MacOSPlatform(available: "12.0") + MacOSPlatform(available: "12.0"), + WindowsPlatform( + available: '1.0.774.44', + apiName: 'ICoreWebView2Controller2.put_DefaultBackgroundColor', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller2?view=webview2-1.0.2210.55#put_defaultbackgroundcolor') ]) bool? transparentBackground; @@ -322,8 +367,15 @@ because there isn't any way to make the website data store non-persistent for th bool? disableHorizontalScroll; ///Set to `true` to disable context menu. The default value is `false`. - @SupportedPlatforms( - platforms: [AndroidPlatform(), IOSPlatform(), WebPlatform()]) + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + WebPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_AreDefaultContextMenusEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredefaultcontextmenusenabled") + ]) bool? disableContextMenu; ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. @@ -333,7 +385,11 @@ because there isn't any way to make the website data store non-persistent for th apiUrl: "https://developer.android.com/reference/android/webkit/WebSettings?hl=en#setSupportZoom(boolean)"), IOSPlatform(), - MacOSPlatform() + MacOSPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsZoomControlEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_iszoomcontrolenabled") ]) bool? supportZoom; @@ -401,6 +457,7 @@ because there isn't any way to make the website data store non-persistent for th ///Use [PlatformCookieManager.removeSessionCookies] instead. @Deprecated("Use CookieManager.removeSessionCookies instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? clearSessionCache; @@ -590,7 +647,17 @@ because there isn't any way to make the website data store non-persistent for th ]) String? fixedFontFamily; + ///Use [algorithmicDarkeningAllowed] instead. + /// ///Set the force dark mode for this WebView. The default value is [ForceDark.OFF]. + /// + ///Deprecated - The "force dark" model previously implemented by WebView was complex and didn't + ///interoperate well with current Web standards for `prefers-color-scheme` and `color-scheme`. + ///In apps with `targetSdkVersion` ≥ `android.os.Build.VERSION_CODES.TIRAMISU` this API is a no-op and + ///WebView will always use the dark style defined by web content authors if the app's theme is dark. + ///To customize the behavior, refer to [algorithmicDarkeningAllowed]. + @Deprecated("Use algorithmicDarkeningAllowed instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( available: "29", @@ -600,10 +667,18 @@ because there isn't any way to make the website data store non-persistent for th ]) ForceDark_? forceDark; - ///Sets whether Geolocation API is enabled. The default value is `true`. - + ///Use [algorithmicDarkeningAllowed] instead. + /// ///Set how WebView content should be darkened. ///The default value is [ForceDarkStrategy.PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING]. + /// + ///Deprecated - The "force dark" model previously implemented by WebView was complex and didn't + ///interoperate well with current Web standards for `prefers-color-scheme` and `color-scheme`. + ///In apps with `targetSdkVersion` ≥ `android.os.Build.VERSION_CODES.TIRAMISU` this API is a no-op and + ///WebView will always use the dark style defined by web content authors if the app's theme is dark. + ///To customize the behavior, refer to [algorithmicDarkeningAllowed]. + @Deprecated("Use algorithmicDarkeningAllowed instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( apiName: "WebSettingsCompat.setForceDarkStrategy", @@ -732,6 +807,9 @@ because there isn't any way to make the website data store non-persistent for th ///Sets whether the WebView should save form data. In Android O, the platform has implemented a fully functional Autofill feature to store form data. ///Therefore, the Webview form data save feature is disabled. Note that the feature will continue to be supported on older versions of Android as before. ///The default value is `true`. + @Deprecated('') + @ExchangeableObjectProperty( + leaveDeprecatedInToMapMethod: true, leaveDeprecatedInFromMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( apiName: "WebSettings.setSaveFormData", @@ -772,11 +850,19 @@ because there isn't any way to make the website data store non-persistent for th ]) bool? supportMultipleWindows; - ///Regular expression used by [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event to cancel navigation requests for frames that are not the main frame. - ///If the url request of a subframe matches the regular expression, then the request of that subframe is canceled. + ///Regular expression used on native side by the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] + ///event to cancel navigation requests for frames that are not the main frame. + ///If the url request of a sub-frame matches the regular expression, then the request of that sub-frame is canceled. @SupportedPlatforms(platforms: [AndroidPlatform()]) String? regexToCancelSubFramesLoading; + ///Regular expression used on native side by the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] + ///event to allow navigation requests synchronously. + ///If the url request match the regular expression, then the request is allowed automatically, + ///and the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event will not be fired. + @SupportedPlatforms(platforms: [AndroidPlatform()]) + String? regexToAllowSyncUrlLoading; + ///Set to `false` to disable Flutter Hybrid Composition. The default value is `true`. ///Hybrid Composition is supported starting with Flutter v1.20+. @SupportedPlatforms(platforms: [ @@ -887,7 +973,13 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ///Sets whether the default Android WebView’s internal error page should be suppressed or displayed for bad navigations. ///`true` means suppressed (not shown), `false` means it will be displayed. The default value is `false`. - @SupportedPlatforms(platforms: [AndroidPlatform()]) + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsBuiltInErrorPageEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2849.39#put_isbuiltinerrorpageenabled'), + ]) bool? disableDefaultErrorPage; ///Sets the vertical scrollbar thumb color. @@ -1025,7 +1117,18 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ]) bool? allowsAirPlayForMediaPlayback; - ///Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. + ///Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. + /// + ///**NOTE for Windows**: Swiping down to refresh is off by default and not exposed via API currently, + ///it requires the "--pull-to-refresh" option to be included in + ///the additional browser arguments to be configured. + ///(See [WebViewEnvironmentSettings.additionalBrowserArguments].). + ///When set to `false`, the end user cannot swipe to navigate or pull to refresh. + ///This API only affects the overscrolling navigation functionality and has + ///no effect on the scrolling interaction used to explore the web content shown in WebView2. + ///Disabling/Enabling [allowsBackForwardNavigationGestures] takes effect after the next navigation. + /// + ///The default value is `true`. @SupportedPlatforms(platforms: [ IOSPlatform( apiName: "WKWebView.allowsBackForwardNavigationGestures", @@ -1034,7 +1137,12 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri MacOSPlatform( apiName: "WKWebView.allowsBackForwardNavigationGestures", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu") + "https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu"), + WindowsPlatform( + available: "1.0.992.28", + apiName: "ICoreWebView2Settings6.put_IsSwipeNavigationEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings6?view=webview2-1.0.2849.39#put_isswipenavigationenabled"), ]) bool? allowsBackForwardNavigationGestures; @@ -1537,7 +1645,11 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri available: "13.3", apiName: "WKWebView.isInspectable", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable") + "https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable"), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_AreDevToolsEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredevtoolsenabled") ]) bool? isInspectable; @@ -1558,6 +1670,353 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ]) bool? shouldPrintBackgrounds; + ///A [Set] of Regular Expression Patterns that will be used on native side to match the allowed origins + ///that are able to execute the JavaScript Handlers defined for the current WebView. + ///This will affect also the internal JavaScript Handlers used by the plugin itself. + /// + ///An empty [Set] will block every origin. + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? javaScriptHandlersOriginAllowList; + + ///Set to `true` to allow to execute the JavaScript Handlers only on the main frame. + ///This will affect also the internal JavaScript Handlers used by the plugin itself. + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptHandlersForMainFrameOnly; + + ///Set to `false` to disable the JavaScript Bridge completely. + ///This will affect also all the internal plugin [UserScript]s + ///that are using the JavaScript Bridge to work. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptBridgeEnabled; + + ///A [Set] of patterns that will be used to match the allowed origins where + ///the JavaScript Bridge could be used. + ///If [pluginScriptsOriginAllowList] is present, then this value will override + ///it only for the JavaScript Bridge internal plugin. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin and, in this case, + ///it will force the behaviour of the [javaScriptBridgeEnabled] parameter, + ///as it was set to `false`. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? javaScriptBridgeOriginAllowList; + + ///Set to `true` to allow the JavaScript Bridge only on the main frame. + ///If [pluginScriptsForMainFrameOnly] is present, then this value will override + ///it only for the JavaScript Bridge internal plugin. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptBridgeForMainFrameOnly; + + ///A [Set] of patterns that will be used to match the allowed origins + ///that are able to load all the internal plugin [UserScript]s used by the plugin itself. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. + /// + ///**NOTE**: If [javaScriptBridgeOriginAllowList] is not present, this value will affect also the JavaScript Bridge internal plugin. + ///Also, setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? pluginScriptsOriginAllowList; + + ///Set to `true` to allow internal plugin [UserScript]s only on the main frame. + /// + ///**NOTE**: If [javaScriptBridgeForMainFrameOnly] is not present, this value will affect also the JavaScript Bridge internal plugin. + ///Also, setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? pluginScriptsForMainFrameOnly; + + ///The multiplier applied to the scroll amount for the WebView. + /// + ///This value determines how much the content will scroll in response to user input. + ///A higher value means faster scrolling, while a lower value means slower scrolling. + /// + ///The default value is `1`. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? scrollMultiplier; + + ///Specifies whether the status bar is displayed. + /// + ///The status bar is usually displayed in the lower left of the WebView and + ///shows things such as the URI of a link when the user hovers over it and other information. + ///The status bar UI can be altered by web content and should not be considered secure. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsStatusBarEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2849.39#put_isstatusbarenabled'), + ]) + bool? statusBarEnabled; + + ///When this setting is set to `false`, it disables all accelerator keys + ///that access features specific to a web browser, including but not limited to: + ///- Ctrl-F and F3 for Find on Page + ///- Ctrl-P for Print + ///- Ctrl-R and F5 for Reload + ///- Ctrl-Plus and Ctrl-Minus for zooming + ///- Ctrl-Shift-C and F12 for DevTools + ///Special keys for browser functions, such as Back, Forward, and Search + ///It does not disable accelerator keys related to movement and text editing, such as: + ///- Home, End, Page Up, and Page Down + ///- Ctrl-X, Ctrl-C, Ctrl-V + ///- Ctrl-A for Select All + ///- Ctrl-Z for Undo + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.864.35', + apiName: "ICoreWebView2Settings3.put_IsBuiltInErrorPageEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings3?view=webview2-1.0.2849.39#put_arebrowseracceleratorkeysenabled'), + ]) + bool? browserAcceleratorKeysEnabled; + + ///Specifies whether autofill for information like names, street and email addresses, phone numbers, and arbitrary input is enabled. + /// + ///This excludes password and credit card information. + ///When [generalAutofillEnabled] is `false`, no suggestions appear, and no new information is saved. + ///When [generalAutofillEnabled] is `true`, information is saved, suggestions appear + ///and clicking on one will populate the form fields. + ///It will take effect immediately after setting. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings4.put_IsGeneralAutofillEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings4?view=webview2-1.0.2849.39#put_isgeneralautofillenabled'), + ]) + bool? generalAutofillEnabled; + + ///Specifies whether autosave for password information is enabled. + /// + ///The [passwordAutosaveEnabled] property behaves independently of the IsGeneralAutofillEnabled property. + ///When [passwordAutosaveEnabled] is `false`, no new password data is saved and no Save/Update Password prompts are displayed. + ///However, if there was password data already saved before disabling this setting, then that password + ///information is auto-populated, suggestions are shown and clicking on one will populate the fields. + ///When [passwordAutosaveEnabled] is `true`, password information is auto-populated, + ///suggestions are shown and clicking on one will populate the fields, + ///new data is saved, and a Save/Update Password prompt is displayed. + ///It will take effect immediately after setting. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings4.put_IsPasswordAutosaveEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings4?view=webview2-1.0.2849.39#put_ispasswordautosaveenabled'), + ]) + bool? passwordAutosaveEnabled; + + ///Pinch-zoom, referred to as "Page Scale" zoom, is performed as a post-rendering step, + ///it changes the page scale factor property and scales the surface the web page + ///is rendered onto when user performs a pinch zooming action. + /// + ///It does not change the layout but rather changes the viewport and clips the + ///web content, the content outside of the viewport isn't visible onscreen and users can't reach this content using mouse. + /// + ///The [pinchZoomEnabled] property enables or disables the ability of the end user + ///to use a pinching motion on touch input enabled devices to scale the web content in the WebView2. + ///When set to `false`, the end user cannot pinch zoom after the next navigation. + ///Disabling/Enabling [pinchZoomEnabled] only affects the end user's ability to + ///use pinch motions and does not change the page scale factor. + ///This API only affects the Page Scale zoom and has no effect on the existing + ///browser zoom properties or other end user mechanisms for zooming. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings5.put_IsPinchZoomEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings5?view=webview2-1.0.2849.39#put_ispinchzoomenabled'), + ]) + bool? pinchZoomEnabled; + + ///This property is used to customize the PDF toolbar items. + /// + ///By default, it is [PdfToolbarItems.NONE] and so it displays all of the items. + ///Changes to this property apply to all CoreWebView2s in the same environment and using the same profile. + ///Changes to this setting apply only after the next navigation. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1185.39', + apiName: "ICoreWebView2Settings7.put_HiddenPdfToolbarItems", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings7?view=webview2-1.0.2849.39#put_hiddenpdftoolbaritems'), + ]) + PdfToolbarItems_? hiddenPdfToolbarItems; + + ///[reputationCheckingRequired] is used to control whether SmartScreen enabled or not. + /// + ///SmartScreen helps webviews identify reported phishing and malware websites and also helps users make informed decisions about downloads. + ///SmartScreen is enabled or disabled for all CoreWebView2s using the same user data folder. + ///If [reputationCheckingRequired] is true for any CoreWebView2 using the same user data folder, then SmartScreen is enabled. + ///If [reputationCheckingRequired] is false for all CoreWebView2 using the same user data folder, then SmartScreen is disabled. + ///When it is changed, the change will be applied to all WebViews using the same user data folder on the next navigation or download. + ///If the newly created CoreWebview2 does not set SmartScreen to `false`, + ///when navigating(Such as Navigate(), LoadDataUrl(), ExecuteScript(), etc.), the default value will be applied to all CoreWebview2 using the same user data folder. + ///SmartScreen of WebView2 apps can be controlled by Windows system setting "SmartScreen for Microsoft Edge", specially, + ///for WebView2 in Windows Store apps, SmartScreen is controlled by another Windows system setting "SmartScreen for Microsoft Store apps". + ///When the Windows setting is enabled, the SmartScreen operates under the control of the [reputationCheckingRequired]. + ///When the Windows setting is disabled, the SmartScreen will be disabled regardless of the [reputationCheckingRequired] value set in WebView2 apps. + ///In other words, under this circumstance the value of [reputationCheckingRequired] will be saved but overridden by system setting. + ///Upon re-enabling the Windows setting, the CoreWebview2 will reference the [reputationCheckingRequired] to determine the SmartScreen status. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1722.45', + apiName: "ICoreWebView2Settings8.put_IsReputationCheckingRequired", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings8?view=webview2-1.0.2849.39#put_isreputationcheckingrequired'), + ]) + bool? reputationCheckingRequired; + + ///Enables web pages to use the `app-region` CSS style. + /// + ///Disabling/Enabling the [nonClientRegionSupportEnabled] takes effect after the next navigation. + /// + ///When this property is `true`, then all the non-client region features will be enabled: + ///Draggable Regions will be enabled, they are regions on a webpage that are marked with the CSS attribute `app-region: drag/no-drag`. + ///When set to drag, these regions will be treated like the window's title bar, + ///supporting dragging of the entire WebView and its host app window; + ///the system menu shows upon right click, and a double click will trigger maximizing/restoration of the window size. + /// + ///When set to `false`, all non-client region support will be disabled. + ///The `app-region` CSS style will be ignored on web pages. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2420.47', + apiName: "ICoreWebView2Settings9.put_IsNonClientRegionSupportEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings9?view=webview2-1.0.2849.39#put_isnonclientregionsupportenabled'), + ]) + bool? nonClientRegionSupportEnabled; + + ///A Boolean value that determines whether user events are ignored and removed from the event queue. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform( + apiName: "UIView.isUserInteractionEnabled", + apiUrl: + 'https://developer.apple.com/documentation/uikit/uiview/1622577-isuserinteractionenabled'), + ]) + bool? isUserInteractionEnabled; + + ///A Boolean value that determines whether to listen and handle the + ///[PlatformWebViewCreationParams.onAcceleratorKeyPressed] event. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + bool? handleAcceleratorKeyPressed; + + ///The view’s alpha value. The value of this property is a floating-point number + ///in the range 0.0 to 1.0, where 0.0 represents totally transparent and 1.0 represents totally opaque. + @SupportedPlatforms(platforms: [ + AndroidPlatform( + apiName: "View.setAlpha", + apiUrl: + 'https://developer.android.com/reference/android/view/View#setAlpha(float)'), + IOSPlatform( + apiName: "UIView.alpha", + apiUrl: + 'https://developer.apple.com/documentation/uikit/uiview/1622417-alpha'), + MacOSPlatform( + apiName: "NSView.alphaValue", + apiUrl: + 'https://developer.apple.com/documentation/appkit/nsview/1483560-alphavalue'), + ]) + double? alpha; + + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onShowFileChooser] event. + /// + ///If the [PlatformWebViewCreationParams.onShowFileChooser] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform()]) + bool? useOnShowFileChooser; + ///Specifies a feature policy for the `. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? name; + + ///The unique identifier of the frame associated with the current [FrameInfo]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? frameId; + + ///The kind of the frame. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + FrameKind_? kind; + FrameInfo_( - {required this.isMainFrame, required this.request, this.securityOrigin}); + {required this.isMainFrame, + required this.request, + this.securityOrigin, + this.name, + this.frameId, + this.kind}); } ///An object that contains information about a frame on a webpage. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart index 71f0402a5..967e94d44 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart @@ -8,36 +8,92 @@ part of 'frame_info.dart'; ///An object that contains information about a frame on a webpage. class FrameInfo { + ///The unique identifier of the frame associated with the current [FrameInfo]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? frameId; + ///A Boolean value indicating whether the frame is the web site's main frame or a subframe. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows bool isMainFrame; + ///The kind of the frame. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + FrameKind? kind; + + ///Gets the name attribute of the frame, as in . + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? name; + ///The frame’s current request. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows URLRequest? request; ///The frame’s security origin. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows SecurityOrigin? securityOrigin; - FrameInfo({required this.isMainFrame, this.request, this.securityOrigin}); + FrameInfo( + {this.frameId, + required this.isMainFrame, + this.kind, + this.name, + this.request, + this.securityOrigin}); ///Gets a possible [FrameInfo] instance from a [Map] value. - static FrameInfo? fromMap(Map? map) { + static FrameInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = FrameInfo( + frameId: map['frameId'], isMainFrame: map['isMainFrame'], - request: URLRequest.fromMap(map['request']?.cast()), + kind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => FrameKind.fromNativeValue(map['kind']), + EnumMethod.value => FrameKind.fromValue(map['kind']), + EnumMethod.name => FrameKind.byName(map['kind']) + }, + name: map['name'], + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod), securityOrigin: SecurityOrigin.fromMap( - map['securityOrigin']?.cast()), + map['securityOrigin']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { + "frameId": frameId, "isMainFrame": isMainFrame, - "request": request?.toMap(), - "securityOrigin": securityOrigin?.toMap(), + "kind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => kind?.toNativeValue(), + EnumMethod.value => kind?.toValue(), + EnumMethod.name => kind?.name() + }, + "name": name, + "request": request?.toMap(enumMethod: enumMethod), + "securityOrigin": securityOrigin?.toMap(enumMethod: enumMethod), }; } @@ -48,7 +104,7 @@ class FrameInfo { @override String toString() { - return 'FrameInfo{isMainFrame: $isMainFrame, request: $request, securityOrigin: $securityOrigin}'; + return 'FrameInfo{frameId: $frameId, isMainFrame: $isMainFrame, kind: $kind, name: $name, request: $request, securityOrigin: $securityOrigin}'; } } @@ -71,25 +127,28 @@ class IOSWKFrameInfo { {required this.isMainFrame, this.request, this.securityOrigin}); ///Gets a possible [IOSWKFrameInfo] instance from a [Map] value. - static IOSWKFrameInfo? fromMap(Map? map) { + static IOSWKFrameInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKFrameInfo( isMainFrame: map['isMainFrame'], - request: URLRequest.fromMap(map['request']?.cast()), + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod), securityOrigin: IOSWKSecurityOrigin.fromMap( - map['securityOrigin']?.cast()), + map['securityOrigin']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, - "request": request?.toMap(), - "securityOrigin": securityOrigin?.toMap(), + "request": request?.toMap(enumMethod: enumMethod), + "securityOrigin": securityOrigin?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart new file mode 100644 index 000000000..c3a6c6965 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart @@ -0,0 +1,64 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'frame_kind.g.dart'; + +///Class used to indicate the the frame kind. +@ExchangeableEnum() +class FrameKind_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const FrameKind_._internal(this._value); + + ///Indicates that the frame is an unknown type frame. We may extend this enum type to identify more frame kinds in the future. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_UNKNOWN', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 0), + ]) + static const UNKNOWN = const FrameKind_._internal('UNKNOWN'); + + ///Indicates that the frame is a primary main frame(webview). + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_MAIN_FRAME', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 1), + ]) + static const MAIN_FRAME = const FrameKind_._internal('MAIN_FRAME'); + + ///Indicates that the frame is an iframe. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_IFRAME', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 2), + ]) + static const IFRAME = const FrameKind_._internal('IFRAME'); + + ///Indicates that the frame is an embed element. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_EMBED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 3), + ]) + static const EMBED = const FrameKind_._internal('EMBED'); + + ///Indicates that the frame is an object element. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_OBJECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 4), + ]) + static const OBJECT = const FrameKind_._internal('OBJECT'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart new file mode 100644 index 000000000..a4162c3e0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart @@ -0,0 +1,185 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'frame_kind.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the the frame kind. +class FrameKind { + final String _value; + final dynamic _nativeValue; + const FrameKind._internal(this._value, this._nativeValue); +// ignore: unused_element + factory FrameKind._internalMultiPlatform( + String value, Function nativeValue) => + FrameKind._internal(value, nativeValue()); + + ///Indicates that the frame is an embed element. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_EMBED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final EMBED = FrameKind._internalMultiPlatform('EMBED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an iframe. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_IFRAME](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final IFRAME = FrameKind._internalMultiPlatform('IFRAME', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///Indicates that the frame is a primary main frame(webview). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_MAIN_FRAME](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final MAIN_FRAME = FrameKind._internalMultiPlatform('MAIN_FRAME', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an object element. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_OBJECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final OBJECT = FrameKind._internalMultiPlatform('OBJECT', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an unknown type frame. We may extend this enum type to identify more frame kinds in the future. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_UNKNOWN](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final UNKNOWN = FrameKind._internalMultiPlatform('UNKNOWN', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///Set of all values of [FrameKind]. + static final Set values = [ + FrameKind.EMBED, + FrameKind.IFRAME, + FrameKind.MAIN_FRAME, + FrameKind.OBJECT, + FrameKind.UNKNOWN, + ].toSet(); + + ///Gets a possible [FrameKind] instance from [String] value. + static FrameKind? fromValue(String? value) { + if (value != null) { + try { + return FrameKind.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [FrameKind] instance from a native value. + static FrameKind? fromNativeValue(dynamic value) { + if (value != null) { + try { + return FrameKind.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [FrameKind] instance value with name [name]. + /// + /// Goes through [FrameKind.values] looking for a value with + /// name [name], as reported by [FrameKind.name]. + /// Returns the first value with the given name, otherwise `null`. + static FrameKind? byName(String? name) { + if (name != null) { + try { + return FrameKind.values.firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [FrameKind] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in FrameKind.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'EMBED': + return 'EMBED'; + case 'IFRAME': + return 'IFRAME'; + case 'MAIN_FRAME': + return 'MAIN_FRAME'; + case 'OBJECT': + return 'OBJECT'; + case 'UNKNOWN': + return 'UNKNOWN'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart index fa4cd16d6..b7ff1e847 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart @@ -1,5 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + part 'geolocation_permission_show_prompt_response.g.dart'; ///Class used by the host application to set the Geolocation permission state for an origin during the [PlatformWebViewCreationParams.onGeolocationPermissionsShowPrompt] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart index 37be906a5..d2d98a9cf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart @@ -22,7 +22,8 @@ class GeolocationPermissionShowPromptResponse { ///Gets a possible [GeolocationPermissionShowPromptResponse] instance from a [Map] value. static GeolocationPermissionShowPromptResponse? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -30,12 +31,14 @@ class GeolocationPermissionShowPromptResponse { allow: map['allow'], origin: map['origin'], ); - instance.retain = map['retain']; + if (map['retain'] != null) { + instance.retain = map['retain']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allow": allow, "origin": origin, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart index ce0340eb9..1f06fd0ce 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'http_auth_response_action.dart'; +import 'enum_method.dart'; part 'http_auth_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart index dfa18193a..0cf972f32 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart @@ -26,22 +26,38 @@ class HttpAuthResponse { this.username = ""}); ///Gets a possible [HttpAuthResponse] instance from a [Map] value. - static HttpAuthResponse? fromMap(Map? map) { + static HttpAuthResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = HttpAuthResponse(); - instance.action = HttpAuthResponseAction.fromNativeValue(map['action']); - instance.password = map['password']; - instance.permanentPersistence = map['permanentPersistence']; - instance.username = map['username']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + HttpAuthResponseAction.fromNativeValue(map['action']), + EnumMethod.value => HttpAuthResponseAction.fromValue(map['action']), + EnumMethod.name => HttpAuthResponseAction.byName(map['action']) + }; + if (map['password'] != null) { + instance.password = map['password']; + } + if (map['permanentPersistence'] != null) { + instance.permanentPersistence = map['permanentPersistence']; + } + if (map['username'] != null) { + instance.username = map['username']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "password": password, "permanentPersistence": permanentPersistence, "username": username, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart index dbd5c5871..1199a0dca 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart @@ -12,12 +12,29 @@ class HttpAuthResponseAction_ { const HttpAuthResponseAction_._internal(this._value); ///Instructs the WebView to cancel the authentication request. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const CANCEL = const HttpAuthResponseAction_._internal(0); ///Instructs the WebView to proceed with the authentication with the given credentials. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const PROCEED = const HttpAuthResponseAction_._internal(1); ///Uses the credentials stored for the current host. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + ]) static const USE_SAVED_HTTP_AUTH_CREDENTIALS = const HttpAuthResponseAction_._internal(2); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart index 320124311..664ce22d8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart @@ -17,12 +17,29 @@ class HttpAuthResponseAction { HttpAuthResponseAction._internal(value, nativeValue()); ///Instructs the WebView to cancel the authentication request. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const CANCEL = HttpAuthResponseAction._internal(0, 0); ///Instructs the WebView to proceed with the authentication with the given credentials. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const PROCEED = HttpAuthResponseAction._internal(1, 1); ///Uses the credentials stored for the current host. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS static const USE_SAVED_HTTP_AUTH_CREDENTIALS = HttpAuthResponseAction._internal(2, 2); @@ -59,20 +76,44 @@ class HttpAuthResponseAction { return null; } + /// Gets a possible [HttpAuthResponseAction] instance value with name [name]. + /// + /// Goes through [HttpAuthResponseAction.values] looking for a value with + /// name [name], as reported by [HttpAuthResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static HttpAuthResponseAction? byName(String? name) { + if (name != null) { + try { + return HttpAuthResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [HttpAuthResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in HttpAuthResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'CANCEL'; @@ -83,4 +124,15 @@ class HttpAuthResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart index 227ae10da..eb7dcc944 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart @@ -5,6 +5,7 @@ import 'url_response.dart'; import 'url_authentication_challenge.dart'; import 'url_protection_space.dart'; import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; part 'http_authentication_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart index 3481661d9..947d84e85 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart @@ -54,34 +54,39 @@ class HttpAuthenticationChallenge extends URLAuthenticationChallenge { } ///Gets a possible [HttpAuthenticationChallenge] instance from a [Map] value. - static HttpAuthenticationChallenge? fromMap(Map? map) { + static HttpAuthenticationChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = HttpAuthenticationChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, error: map['error'], - failureResponse: - URLResponse.fromMap(map['failureResponse']?.cast()), + failureResponse: URLResponse.fromMap( + map['failureResponse']?.cast(), + enumMethod: enumMethod), iosError: map['error'], iosFailureResponse: IOSURLResponse.fromMap( - map['failureResponse']?.cast()), + map['failureResponse']?.cast(), + enumMethod: enumMethod), previousFailureCount: map['previousFailureCount'], proposedCredential: URLCredential.fromMap( - map['proposedCredential']?.cast()), + map['proposedCredential']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), "error": error, - "failureResponse": failureResponse?.toMap(), + "failureResponse": failureResponse?.toMap(enumMethod: enumMethod), "previousFailureCount": previousFailureCount, - "proposedCredential": proposedCredential?.toMap(), + "proposedCredential": proposedCredential?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart index c21d5381b..7da454fc6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart @@ -66,12 +66,55 @@ class HTTPCookieSameSitePolicy { return null; } + /// Gets a possible [HTTPCookieSameSitePolicy] instance value with name [name]. + /// + /// Goes through [HTTPCookieSameSitePolicy.values] looking for a value with + /// name [name], as reported by [HTTPCookieSameSitePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static HTTPCookieSameSitePolicy? byName(String? name) { + if (name != null) { + try { + return HTTPCookieSameSitePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [HTTPCookieSameSitePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in HTTPCookieSameSitePolicy.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'Lax': + return 'LAX'; + case 'None': + return 'NONE'; + case 'Strict': + return 'STRICT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart index cd5836305..7299bec02 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'in_app_webview_hit_test_result_type.dart'; +import 'enum_method.dart'; part 'in_app_webview_hit_test_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart index c022ecffe..5e719439f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart @@ -16,22 +16,33 @@ class InAppWebViewHitTestResult { InAppWebViewHitTestResult({this.extra, this.type}); ///Gets a possible [InAppWebViewHitTestResult] instance from a [Map] value. - static InAppWebViewHitTestResult? fromMap(Map? map) { + static InAppWebViewHitTestResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = InAppWebViewHitTestResult( extra: map['extra'], - type: InAppWebViewHitTestResultType.fromNativeValue(map['type']), + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + InAppWebViewHitTestResultType.fromNativeValue(map['type']), + EnumMethod.value => + InAppWebViewHitTestResultType.fromValue(map['type']), + EnumMethod.name => InAppWebViewHitTestResultType.byName(map['type']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "extra": extra, - "type": type?.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type?.toNativeValue(), + EnumMethod.value => type?.toValue(), + EnumMethod.name => type?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart index 477e2b9bf..c395c99f3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart @@ -79,20 +79,45 @@ class InAppWebViewHitTestResultType { return null; } + /// Gets a possible [InAppWebViewHitTestResultType] instance value with name [name]. + /// + /// Goes through [InAppWebViewHitTestResultType.values] looking for a value with + /// name [name], as reported by [InAppWebViewHitTestResultType.name]. + /// Returns the first value with the given name, otherwise `null`. + static InAppWebViewHitTestResultType? byName(String? name) { + if (name != null) { + try { + return InAppWebViewHitTestResultType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [InAppWebViewHitTestResultType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in InAppWebViewHitTestResultType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'EDIT_TEXT_TYPE'; @@ -113,4 +138,15 @@ class InAppWebViewHitTestResultType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart index 7a26edb3c..dae877f25 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'in_app_webview_initial_data.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart index 4cfd6cc9f..bfe52cee7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart @@ -41,7 +41,8 @@ class InAppWebViewInitialData { } ///Gets a possible [InAppWebViewInitialData] instance from a [Map] value. - static InAppWebViewInitialData? fromMap(Map? map) { + static InAppWebViewInitialData? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -52,13 +53,17 @@ class InAppWebViewInitialData { data: map['data'], historyUrl: map['historyUrl'] != null ? WebUri(map['historyUrl']) : null, ); - instance.encoding = map['encoding']; - instance.mimeType = map['mimeType']; + if (map['encoding'] != null) { + instance.encoding = map['encoding']; + } + if (map['mimeType'] != null) { + instance.mimeType = map['mimeType']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "baseUrl": baseUrl?.toString(), "data": data, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart index e0c4775b7..f3f3992ad 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'in_app_webview_rect.g.dart'; ///A class that represents a structure that contains the location and dimensions of a rectangle. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart index fb059d53a..218566fa8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart @@ -28,7 +28,8 @@ class InAppWebViewRect { } ///Gets a possible [InAppWebViewRect] instance from a [Map] value. - static InAppWebViewRect? fromMap(Map? map) { + static InAppWebViewRect? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -42,7 +43,7 @@ class InAppWebViewRect { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "height": height, "width": width, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart index c0a7a7600..8f964e3af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart @@ -1,15 +1,37 @@ import 'dart:convert'; -import '../in_app_webview/platform_inappwebview_controller.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +part 'javascript_handler_callback.g.dart'; + +///Use [JavaScriptHandlerFunction] instead. +@Deprecated('Use JavaScriptHandlerFunction instead') +typedef dynamic JavaScriptHandlerCallback(List arguments); ///This type represents a callback, added with [PlatformInAppWebViewController.addJavaScriptHandler], that listens to post messages sent from JavaScript. /// ///The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)). -///The iOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc) +///The iOS/macOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc) /// ///The JavaScript function that can be used to call the handler is `window.flutter_inappwebview.callHandler(handlerName , ...args);`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters). ///The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side. /// -///Also, a [JavaScriptHandlerCallback] can return json data to the JavaScript side. +///Also, a [JavaScriptHandlerFunction] can return json data to the JavaScript side. ///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library. -typedef dynamic JavaScriptHandlerCallback(List arguments); +typedef dynamic JavaScriptHandlerFunction(JavaScriptHandlerFunctionData data); + +///A class that represents the data passed to a [JavaScriptHandlerFunction] added with [PlatformInAppWebViewController.addJavaScriptHandler]. +@ExchangeableObject() +class JavaScriptHandlerFunctionData_ { + List args; + WebUri origin; + bool isMainFrame; + WebUri requestUrl; + + JavaScriptHandlerFunctionData_( + {this.args = const [], + required this.origin, + required this.isMainFrame, + required this.requestUrl}); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart new file mode 100644 index 000000000..253917169 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'javascript_handler_callback.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///A class that represents the data passed to a [JavaScriptHandlerFunction] added with [PlatformInAppWebViewController.addJavaScriptHandler]. +class JavaScriptHandlerFunctionData { + List args; + bool isMainFrame; + WebUri origin; + WebUri requestUrl; + JavaScriptHandlerFunctionData( + {this.args = const [], + required this.isMainFrame, + required this.origin, + required this.requestUrl}); + + ///Gets a possible [JavaScriptHandlerFunctionData] instance from a [Map] value. + static JavaScriptHandlerFunctionData? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = JavaScriptHandlerFunctionData( + isMainFrame: map['isMainFrame'], + origin: WebUri(map['origin']), + requestUrl: WebUri(map['requestUrl']), + ); + if (map['args'] != null) { + instance.args = List.from(map['args']!.cast()); + } + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "args": args, + "isMainFrame": isMainFrame, + "origin": origin.toString(), + "requestUrl": requestUrl.toString(), + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'JavaScriptHandlerFunctionData{args: $args, isMainFrame: $isMainFrame, origin: $origin, requestUrl: $requestUrl}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart index 401e7f78b..ad9f66fe9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_alert_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart index 9da0d33b3..1e8951577 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart @@ -33,7 +33,8 @@ class JsAlertRequest { } ///Gets a possible [JsAlertRequest] instance from a [Map] value. - static JsAlertRequest? fromMap(Map? map) { + static JsAlertRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -47,7 +48,7 @@ class JsAlertRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart index 1dbc54232..4666c9b07 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_alert_response_action.dart'; +import 'enum_method.dart'; part 'js_alert_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart index d6903df7c..710c0a0e0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart @@ -26,22 +26,38 @@ class JsAlertResponse { this.message = ""}); ///Gets a possible [JsAlertResponse] instance from a [Map] value. - static JsAlertResponse? fromMap(Map? map) { + static JsAlertResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsAlertResponse(); - instance.action = JsAlertResponseAction.fromNativeValue(map['action']); - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsAlertResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsAlertResponseAction.fromValue(map['action']), + EnumMethod.name => JsAlertResponseAction.byName(map['action']) + }; + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart index 496541321..cef0b42bc 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart @@ -50,12 +50,51 @@ class JsAlertResponseAction { return null; } + /// Gets a possible [JsAlertResponseAction] instance value with name [name]. + /// + /// Goes through [JsAlertResponseAction.values] looking for a value with + /// name [name], as reported by [JsAlertResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsAlertResponseAction? byName(String? name) { + if (name != null) { + try { + return JsAlertResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsAlertResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsAlertResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -64,10 +103,6 @@ class JsAlertResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart index df75b36f9..cba0144af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_before_unload_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart index 0cf1ed4f7..62d70b667 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart @@ -16,7 +16,8 @@ class JsBeforeUnloadRequest { JsBeforeUnloadRequest({this.message, this.url}); ///Gets a possible [JsBeforeUnloadRequest] instance from a [Map] value. - static JsBeforeUnloadRequest? fromMap(Map? map) { + static JsBeforeUnloadRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class JsBeforeUnloadRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "message": message, "url": url?.toString(), diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart index 0170db589..e2d5bb598 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_before_unload_response_action.dart'; +import 'enum_method.dart'; part 'js_before_unload_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart index 885f2f573..40ae46c68 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart @@ -30,24 +30,41 @@ class JsBeforeUnloadResponse { this.message = ""}); ///Gets a possible [JsBeforeUnloadResponse] instance from a [Map] value. - static JsBeforeUnloadResponse? fromMap(Map? map) { + static JsBeforeUnloadResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsBeforeUnloadResponse(); - instance.action = - JsBeforeUnloadResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsBeforeUnloadResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsBeforeUnloadResponseAction.fromValue(map['action']), + EnumMethod.name => JsBeforeUnloadResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart index e82005ccb..eda8d59ce 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart @@ -54,12 +54,54 @@ class JsBeforeUnloadResponseAction { return null; } + /// Gets a possible [JsBeforeUnloadResponseAction] instance value with name [name]. + /// + /// Goes through [JsBeforeUnloadResponseAction.values] looking for a value with + /// name [name], as reported by [JsBeforeUnloadResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsBeforeUnloadResponseAction? byName(String? name) { + if (name != null) { + try { + return JsBeforeUnloadResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsBeforeUnloadResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsBeforeUnloadResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +110,6 @@ class JsBeforeUnloadResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart index efd873029..0e2058389 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_confirm_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart index 3ab660da2..6d3497c3a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart @@ -33,7 +33,8 @@ class JsConfirmRequest { } ///Gets a possible [JsConfirmRequest] instance from a [Map] value. - static JsConfirmRequest? fromMap(Map? map) { + static JsConfirmRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -47,7 +48,7 @@ class JsConfirmRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart index a00083097..3ce0923df 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_confirm_response_action.dart'; +import 'enum_method.dart'; part 'js_confirm_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart index ddd0bc9be..f2eb05587 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart @@ -30,23 +30,41 @@ class JsConfirmResponse { this.message = ""}); ///Gets a possible [JsConfirmResponse] instance from a [Map] value. - static JsConfirmResponse? fromMap(Map? map) { + static JsConfirmResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsConfirmResponse(); - instance.action = JsConfirmResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsConfirmResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsConfirmResponseAction.fromValue(map['action']), + EnumMethod.name => JsConfirmResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart index a9c706061..fff5abf6b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart @@ -54,12 +54,53 @@ class JsConfirmResponseAction { return null; } + /// Gets a possible [JsConfirmResponseAction] instance value with name [name]. + /// + /// Goes through [JsConfirmResponseAction.values] looking for a value with + /// name [name], as reported by [JsConfirmResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsConfirmResponseAction? byName(String? name) { + if (name != null) { + try { + return JsConfirmResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsConfirmResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsConfirmResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +109,6 @@ class JsConfirmResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart index 87dace2c9..d0e497dbf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_prompt_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart index 422d6d179..7ec6fad89 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart @@ -37,7 +37,8 @@ class JsPromptRequest { } ///Gets a possible [JsPromptRequest] instance from a [Map] value. - static JsPromptRequest? fromMap(Map? map) { + static JsPromptRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -52,7 +53,7 @@ class JsPromptRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "defaultValue": defaultValue, "isMainFrame": isMainFrame, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart index e262690b7..c3fa4cc32 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_prompt_response_action.dart'; +import 'enum_method.dart'; part 'js_prompt_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart index abea1745f..de84d987d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart @@ -38,26 +38,46 @@ class JsPromptResponse { this.value}); ///Gets a possible [JsPromptResponse] instance from a [Map] value. - static JsPromptResponse? fromMap(Map? map) { + static JsPromptResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsPromptResponse( value: map['value'], ); - instance.action = JsPromptResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.defaultValue = map['defaultValue']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsPromptResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsPromptResponseAction.fromValue(map['action']), + EnumMethod.name => JsPromptResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['defaultValue'] != null) { + instance.defaultValue = map['defaultValue']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "defaultValue": defaultValue, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart index ddebfa6a4..ff4257fb4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart @@ -54,12 +54,53 @@ class JsPromptResponseAction { return null; } + /// Gets a possible [JsPromptResponseAction] instance value with name [name]. + /// + /// Goes through [JsPromptResponseAction.values] looking for a value with + /// name [name], as reported by [JsPromptResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsPromptResponseAction? byName(String? name) { + if (name != null) { + try { + return JsPromptResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsPromptResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsPromptResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +109,6 @@ class JsPromptResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart index def5e52ad..0f47008ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart @@ -63,12 +63,54 @@ class LayoutAlgorithm { return null; } + /// Gets a possible [LayoutAlgorithm] instance value with name [name]. + /// + /// Goes through [LayoutAlgorithm.values] looking for a value with + /// name [name], as reported by [LayoutAlgorithm.name]. + /// Returns the first value with the given name, otherwise `null`. + static LayoutAlgorithm? byName(String? name) { + if (name != null) { + try { + return LayoutAlgorithm.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [LayoutAlgorithm] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in LayoutAlgorithm.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NARROW_COLUMNS': + return 'NARROW_COLUMNS'; + case 'NORMAL': + return 'NORMAL'; + case 'TEXT_AUTOSIZING': + return 'TEXT_AUTOSIZING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -140,12 +182,55 @@ class AndroidLayoutAlgorithm { return null; } + /// Gets a possible [AndroidLayoutAlgorithm] instance value with name [name]. + /// + /// Goes through [AndroidLayoutAlgorithm.values] looking for a value with + /// name [name], as reported by [AndroidLayoutAlgorithm.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidLayoutAlgorithm? byName(String? name) { + if (name != null) { + try { + return AndroidLayoutAlgorithm.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidLayoutAlgorithm] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidLayoutAlgorithm.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NARROW_COLUMNS': + return 'NARROW_COLUMNS'; + case 'NORMAL': + return 'NORMAL'; + case 'TEXT_AUTOSIZING': + return 'TEXT_AUTOSIZING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart index 67db5301e..db21271b9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart @@ -70,20 +70,45 @@ class LayoutInDisplayCutoutMode { return null; } + /// Gets a possible [LayoutInDisplayCutoutMode] instance value with name [name]. + /// + /// Goes through [LayoutInDisplayCutoutMode.values] looking for a value with + /// name [name], as reported by [LayoutInDisplayCutoutMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static LayoutInDisplayCutoutMode? byName(String? name) { + if (name != null) { + try { + return LayoutInDisplayCutoutMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [LayoutInDisplayCutoutMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in LayoutInDisplayCutoutMode.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -96,6 +121,17 @@ class LayoutInDisplayCutoutMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Android-specific class representing the share state that should be applied to the custom tab. @@ -168,20 +204,45 @@ class AndroidLayoutInDisplayCutoutMode { return null; } + /// Gets a possible [AndroidLayoutInDisplayCutoutMode] instance value with name [name]. + /// + /// Goes through [AndroidLayoutInDisplayCutoutMode.values] looking for a value with + /// name [name], as reported by [AndroidLayoutInDisplayCutoutMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidLayoutInDisplayCutoutMode? byName(String? name) { + if (name != null) { + try { + return AndroidLayoutInDisplayCutoutMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidLayoutInDisplayCutoutMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidLayoutInDisplayCutoutMode.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -194,4 +255,15 @@ class AndroidLayoutInDisplayCutoutMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart index 0b538034d..21c9aca15 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'loaded_resource.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart index 7949c6e57..8993e5adf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart @@ -23,7 +23,8 @@ class LoadedResource { LoadedResource({this.duration, this.initiatorType, this.startTime, this.url}); ///Gets a possible [LoadedResource] instance from a [Map] value. - static LoadedResource? fromMap(Map? map) { + static LoadedResource? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -37,7 +38,7 @@ class LoadedResource { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "duration": duration, "initiatorType": initiatorType, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart index c68e58756..4f209d3c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart @@ -1,5 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + part 'login_request.g.dart'; ///Class used by [PlatformWebViewCreationParams.onReceivedLoginRequest] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart index 70436ab34..f6e2667ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart @@ -20,7 +20,8 @@ class LoginRequest { LoginRequest({this.account, required this.args, required this.realm}); ///Gets a possible [LoginRequest] instance from a [Map] value. - static LoginRequest? fromMap(Map? map) { + static LoginRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class LoginRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "account": account, "args": args, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/main.dart b/flutter_inappwebview_platform_interface/lib/src/types/main.dart index 927193331..4c4dc9149 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/main.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/main.dart @@ -59,7 +59,11 @@ export 'in_app_webview_hit_test_result_type.dart' show InAppWebViewHitTestResultType; export 'in_app_webview_initial_data.dart' show InAppWebViewInitialData; export 'in_app_webview_rect.dart' show InAppWebViewRect; -export 'javascript_handler_callback.dart' show JavaScriptHandlerCallback; +export 'javascript_handler_callback.dart' + show + JavaScriptHandlerCallback, + JavaScriptHandlerFunction, + JavaScriptHandlerFunctionData; export 'js_alert_request.dart' show JsAlertRequest; export 'js_alert_response.dart' show JsAlertResponse; export 'js_alert_response_action.dart' show JsAlertResponseAction; @@ -225,4 +229,30 @@ export 'tracing_mode.dart' show TracingMode; export 'tracing_category.dart' show TracingCategory; export 'custom_tabs_post_message_result_type.dart' show CustomTabsPostMessageResultType; +export 'custom_scheme_registration.dart' show CustomSchemeRegistration; export 'disposable.dart'; +export 'frame_kind.dart' show FrameKind; +export 'process_failed_kind.dart' show ProcessFailedKind; +export 'process_failed_reason.dart' show ProcessFailedReason; +export 'process_failed_detail.dart' show ProcessFailedDetail; +export 'focus_direction.dart' show FocusDirection; +export 'enum_method.dart'; +export 'pdf_toolbar_items.dart' show PdfToolbarItems; +export 'webview_interface.dart' show WebViewInterface; +export 'download_start_response_action.dart' show DownloadStartResponseAction; +export 'download_start_response.dart' show DownloadStartResponse; +export 'environment_channel_search_kind.dart' show EnvironmentChannelSearchKind; +export 'environment_release_channels.dart' show EnvironmentReleaseChannels; +export 'environment_scrollbar_style.dart' show EnvironmentScrollbarStyle; +export 'browser_process_exit_kind.dart' show BrowserProcessExitKind; +export 'browser_process_exited_detail.dart' show BrowserProcessExitedDetail; +export 'browser_process_kind.dart' show BrowserProcessKind; +export 'browser_process_info.dart' show BrowserProcessInfo; +export 'browser_process_infos_changed_detail.dart' + show BrowserProcessInfosChangedDetail; +export 'physical_key_status.dart' show PhysicalKeyStatus; +export 'accelerator_key_pressed_detail.dart' show AcceleratorKeyPressedDetail; +export 'proxy_relay_hop.dart' show ProxyRelayHop; +export 'show_file_chooser_request_mode.dart' show ShowFileChooserRequestMode; +export 'show_file_chooser_request.dart' show ShowFileChooserRequest; +export 'show_file_chooser_response.dart' show ShowFileChooserResponse; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart index 47a0076ae..73cdcad5e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart @@ -58,20 +58,44 @@ class MediaCaptureState { return null; } + /// Gets a possible [MediaCaptureState] instance value with name [name]. + /// + /// Goes through [MediaCaptureState.values] looking for a value with + /// name [name], as reported by [MediaCaptureState.name]. + /// Returns the first value with the given name, otherwise `null`. + static MediaCaptureState? byName(String? name) { + if (name != null) { + try { + return MediaCaptureState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MediaCaptureState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MediaCaptureState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ACTIVE'; @@ -82,4 +106,15 @@ class MediaCaptureState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart index a521269b6..62b8d7642 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart @@ -62,20 +62,44 @@ class MediaPlaybackState { return null; } + /// Gets a possible [MediaPlaybackState] instance value with name [name]. + /// + /// Goes through [MediaPlaybackState.values] looking for a value with + /// name [name], as reported by [MediaPlaybackState.name]. + /// Returns the first value with the given name, otherwise `null`. + static MediaPlaybackState? byName(String? name) { + if (name != null) { + try { + return MediaPlaybackState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MediaPlaybackState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MediaPlaybackState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'NONE'; @@ -88,4 +112,15 @@ class MediaPlaybackState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart index 892e4688a..0d22a6604 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'meta_tag_attribute.dart'; +import 'enum_method.dart'; part 'meta_tag.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart index cbbd3505d..95029ab0d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart @@ -19,14 +19,15 @@ class MetaTag { MetaTag({this.attrs, this.content, this.name}); ///Gets a possible [MetaTag] instance from a [Map] value. - static MetaTag? fromMap(Map? map) { + static MetaTag? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = MetaTag( attrs: map['attrs'] != null - ? List.from(map['attrs'].map( - (e) => MetaTagAttribute.fromMap(e?.cast())!)) + ? List.from(map['attrs'].map((e) => + MetaTagAttribute.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, content: map['content'], name: map['name'], @@ -35,9 +36,9 @@ class MetaTag { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "attrs": attrs?.map((e) => e.toMap()).toList(), + "attrs": attrs?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "content": content, "name": name, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart index 35048ed18..ba25c70c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'meta_tag.dart'; +import 'enum_method.dart'; part 'meta_tag_attribute.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart index 8e546a065..cd663bf8e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart @@ -16,7 +16,8 @@ class MetaTagAttribute { MetaTagAttribute({this.name, this.value}); ///Gets a possible [MetaTagAttribute] instance from a [Map] value. - static MetaTagAttribute? fromMap(Map? map) { + static MetaTagAttribute? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class MetaTagAttribute { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "name": name, "value": value, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart index 23cdf1436..5d75fd1b2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart @@ -65,20 +65,44 @@ class MixedContentMode { return null; } + /// Gets a possible [MixedContentMode] instance value with name [name]. + /// + /// Goes through [MixedContentMode.values] looking for a value with + /// name [name], as reported by [MixedContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static MixedContentMode? byName(String? name) { + if (name != null) { + try { + return MixedContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MixedContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MixedContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'MIXED_CONTENT_ALWAYS_ALLOW'; @@ -89,6 +113,17 @@ class MixedContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the WebView's behavior when a secure origin attempts to load a resource from an insecure origin. @@ -157,20 +192,44 @@ class AndroidMixedContentMode { return null; } + /// Gets a possible [AndroidMixedContentMode] instance value with name [name]. + /// + /// Goes through [AndroidMixedContentMode.values] looking for a value with + /// name [name], as reported by [AndroidMixedContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidMixedContentMode? byName(String? name) { + if (name != null) { + try { + return AndroidMixedContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidMixedContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidMixedContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'MIXED_CONTENT_ALWAYS_ALLOW'; @@ -181,4 +240,15 @@ class AndroidMixedContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart index 71ca9419e..c784d481d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart @@ -88,20 +88,44 @@ class ModalPresentationStyle { return null; } + /// Gets a possible [ModalPresentationStyle] instance value with name [name]. + /// + /// Goes through [ModalPresentationStyle.values] looking for a value with + /// name [name], as reported by [ModalPresentationStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ModalPresentationStyle? byName(String? name) { + if (name != null) { + try { + return ModalPresentationStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ModalPresentationStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ModalPresentationStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'AUTOMATIC'; @@ -126,6 +150,17 @@ class ModalPresentationStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to specify the modal presentation style when presenting a view controller. @@ -213,20 +248,45 @@ class IOSUIModalPresentationStyle { return null; } + /// Gets a possible [IOSUIModalPresentationStyle] instance value with name [name]. + /// + /// Goes through [IOSUIModalPresentationStyle.values] looking for a value with + /// name [name], as reported by [IOSUIModalPresentationStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIModalPresentationStyle? byName(String? name) { + if (name != null) { + try { + return IOSUIModalPresentationStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIModalPresentationStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIModalPresentationStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'AUTOMATIC'; @@ -251,4 +311,15 @@ class IOSUIModalPresentationStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart index a5cd6ff7c..56222c794 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart @@ -68,20 +68,44 @@ class ModalTransitionStyle { return null; } + /// Gets a possible [ModalTransitionStyle] instance value with name [name]. + /// + /// Goes through [ModalTransitionStyle.values] looking for a value with + /// name [name], as reported by [ModalTransitionStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ModalTransitionStyle? byName(String? name) { + if (name != null) { + try { + return ModalTransitionStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ModalTransitionStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ModalTransitionStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'COVER_VERTICAL'; @@ -94,6 +118,17 @@ class ModalTransitionStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to specify the transition style when presenting a view controller. @@ -160,20 +195,45 @@ class IOSUIModalTransitionStyle { return null; } + /// Gets a possible [IOSUIModalTransitionStyle] instance value with name [name]. + /// + /// Goes through [IOSUIModalTransitionStyle.values] looking for a value with + /// name [name], as reported by [IOSUIModalTransitionStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIModalTransitionStyle? byName(String? name) { + if (name != null) { + try { + return IOSUIModalTransitionStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIModalTransitionStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIModalTransitionStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'COVER_VERTICAL'; @@ -186,4 +246,15 @@ class IOSUIModalTransitionStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart index d2495be5a..01bad8270 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_request.dart'; import 'navigation_type.dart'; import 'frame_info.dart'; +import 'enum_method.dart'; part 'navigation_action.g.dart'; @@ -19,7 +20,7 @@ class NavigationAction_ { ///Indicates whether the request was made for the main frame. /// - ///**NOTE for Android**: If the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event, this is always `true`. + ///**NOTE for Android and Windows**: If the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event, this is always `true`. ///Also, on Android < 21, this is always `true`. bool isForMainFrame; @@ -37,7 +38,10 @@ class NavigationAction_ { apiName: "WebResourceRequest.hasGesture", apiUrl: "https://developer.android.com/reference/android/webkit/WebResourceRequest#hasGesture()", - note: "On Android < 21, this is always `false`") + note: "On Android < 21, this is always `false`"), + WindowsPlatform( + note: + "Available only if the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event") ]) bool? hasGesture; @@ -54,7 +58,8 @@ class NavigationAction_ { available: "21", apiName: "WebResourceRequest.isRedirect", apiUrl: - "https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect()") + "https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect()"), + WindowsPlatform() ]) bool? isRedirect; @@ -62,7 +67,7 @@ class NavigationAction_ { @Deprecated("Use navigationType instead") IOSWKNavigationType_? iosWKNavigationType; - ///The type of action triggering the navigation.ì + ///The type of action triggering the navigation. @SupportedPlatforms(platforms: [ IOSPlatform( apiName: "WKNavigationAction.navigationType", @@ -71,7 +76,8 @@ class NavigationAction_ { MacOSPlatform( apiName: "WKNavigationAction.navigationType", apiUrl: - "https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype") + "https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype"), + WindowsPlatform() ]) NavigationType_? navigationType; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart index 9b24689e8..b69730c60 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart @@ -23,8 +23,11 @@ class NavigationAction { /// ///**NOTE for Android native WebView**: On Android < 21, this is always `false` /// + ///**NOTE for Windows**: Available only if the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event + /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView 21+ ([Official API - WebResourceRequest.hasGesture](https://developer.android.com/reference/android/webkit/WebResourceRequest#hasGesture())) + ///- Windows bool? hasGesture; ///Use [sourceFrame] instead. @@ -41,7 +44,7 @@ class NavigationAction { ///Indicates whether the request was made for the main frame. /// - ///**NOTE for Android**: If the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event, this is always `true`. + ///**NOTE for Android and Windows**: If the request is associated to the [PlatformWebViewCreationParams.onCreateWindow] event, this is always `true`. ///Also, on Android < 21, this is always `true`. bool isForMainFrame; @@ -52,13 +55,15 @@ class NavigationAction { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView 21+ ([Official API - WebResourceRequest.isRedirect](https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect())) + ///- Windows bool? isRedirect; - ///The type of action triggering the navigation.ì + ///The type of action triggering the navigation. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationAction.navigationType](https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype)) ///- MacOS ([Official API - WKNavigationAction.navigationType](https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype)) + ///- Windows NavigationType? navigationType; ///The URL request object associated with the navigation action. @@ -112,7 +117,8 @@ class NavigationAction { } ///Gets a possible [NavigationAction] instance from a [Map] value. - static NavigationAction? fromMap(Map? map) { + static NavigationAction? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -120,36 +126,55 @@ class NavigationAction { androidHasGesture: map['hasGesture'], androidIsRedirect: map['isRedirect'], hasGesture: map['hasGesture'], - iosSourceFrame: - IOSWKFrameInfo.fromMap(map['sourceFrame']?.cast()), - iosTargetFrame: - IOSWKFrameInfo.fromMap(map['targetFrame']?.cast()), - iosWKNavigationType: + iosSourceFrame: IOSWKFrameInfo.fromMap( + map['sourceFrame']?.cast(), + enumMethod: enumMethod), + iosTargetFrame: IOSWKFrameInfo.fromMap( + map['targetFrame']?.cast(), + enumMethod: enumMethod), + iosWKNavigationType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSWKNavigationType.fromNativeValue(map['navigationType']), + EnumMethod.value => + IOSWKNavigationType.fromValue(map['navigationType']), + EnumMethod.name => IOSWKNavigationType.byName(map['navigationType']) + }, isForMainFrame: map['isForMainFrame'], isRedirect: map['isRedirect'], - navigationType: NavigationType.fromNativeValue(map['navigationType']), - request: URLRequest.fromMap(map['request']?.cast())!, + navigationType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + NavigationType.fromNativeValue(map['navigationType']), + EnumMethod.value => NavigationType.fromValue(map['navigationType']), + EnumMethod.name => NavigationType.byName(map['navigationType']) + }, + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod)!, shouldPerformDownload: map['shouldPerformDownload'], - sourceFrame: - FrameInfo.fromMap(map['sourceFrame']?.cast()), - targetFrame: - FrameInfo.fromMap(map['targetFrame']?.cast()), + sourceFrame: FrameInfo.fromMap( + map['sourceFrame']?.cast(), + enumMethod: enumMethod), + targetFrame: FrameInfo.fromMap( + map['targetFrame']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hasGesture": hasGesture, "isForMainFrame": isForMainFrame, "isRedirect": isRedirect, - "navigationType": navigationType?.toNativeValue(), - "request": request.toMap(), + "navigationType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => navigationType?.toNativeValue(), + EnumMethod.value => navigationType?.toValue(), + EnumMethod.name => navigationType?.name() + }, + "request": request.toMap(enumMethod: enumMethod), "shouldPerformDownload": shouldPerformDownload, - "sourceFrame": sourceFrame?.toMap(), - "targetFrame": targetFrame?.toMap(), + "sourceFrame": sourceFrame?.toMap(enumMethod: enumMethod), + "targetFrame": targetFrame?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart index 5bca8ddfb..90636aa90 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart @@ -61,20 +61,44 @@ class NavigationActionPolicy { return null; } + /// Gets a possible [NavigationActionPolicy] instance value with name [name]. + /// + /// Goes through [NavigationActionPolicy.values] looking for a value with + /// name [name], as reported by [NavigationActionPolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationActionPolicy? byName(String? name) { + if (name != null) { + try { + return NavigationActionPolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [NavigationActionPolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in NavigationActionPolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ALLOW'; @@ -85,4 +109,15 @@ class NavigationActionPolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart index 0caee8019..19a99f76d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'url_response.dart'; +import 'enum_method.dart'; part 'navigation_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart index 14f5e74a8..12ef553ed 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart @@ -22,24 +22,26 @@ class NavigationResponse { this.response}); ///Gets a possible [NavigationResponse] instance from a [Map] value. - static NavigationResponse? fromMap(Map? map) { + static NavigationResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = NavigationResponse( canShowMIMEType: map['canShowMIMEType'], isForMainFrame: map['isForMainFrame'], - response: URLResponse.fromMap(map['response']?.cast()), + response: URLResponse.fromMap(map['response']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "canShowMIMEType": canShowMIMEType, "isForMainFrame": isForMainFrame, - "response": response?.toMap(), + "response": response?.toMap(enumMethod: enumMethod), }; } @@ -72,25 +74,26 @@ class IOSWKNavigationResponse { this.response}); ///Gets a possible [IOSWKNavigationResponse] instance from a [Map] value. - static IOSWKNavigationResponse? fromMap(Map? map) { + static IOSWKNavigationResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKNavigationResponse( canShowMIMEType: map['canShowMIMEType'], isForMainFrame: map['isForMainFrame'], - response: - IOSURLResponse.fromMap(map['response']?.cast()), + response: IOSURLResponse.fromMap(map['response']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "canShowMIMEType": canShowMIMEType, "isForMainFrame": isForMainFrame, - "response": response?.toMap(), + "response": response?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart index 6c361637d..6f1e4183e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart @@ -61,20 +61,44 @@ class NavigationResponseAction { return null; } + /// Gets a possible [NavigationResponseAction] instance value with name [name]. + /// + /// Goes through [NavigationResponseAction.values] looking for a value with + /// name [name], as reported by [NavigationResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationResponseAction? byName(String? name) { + if (name != null) { + try { + return NavigationResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [NavigationResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in NavigationResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ALLOW'; @@ -85,6 +109,17 @@ class NavigationResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class that is used by [PlatformWebViewCreationParams.onNavigationResponse] event. @@ -138,12 +173,54 @@ class IOSNavigationResponseAction { return null; } + /// Gets a possible [IOSNavigationResponseAction] instance value with name [name]. + /// + /// Goes through [IOSNavigationResponseAction.values] looking for a value with + /// name [name], as reported by [IOSNavigationResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNavigationResponseAction? byName(String? name) { + if (name != null) { + try { + return IOSNavigationResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNavigationResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNavigationResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -152,12 +229,6 @@ class IOSNavigationResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart index 72c07c8c4..c38dfb60e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../in_app_webview/platform_webview.dart'; part 'navigation_type.g.dart'; @@ -6,26 +7,116 @@ part 'navigation_type.g.dart'; @ExchangeableEnum() class NavigationType_ { // ignore: unused_field - final int _value; + final String _value; + // ignore: unused_field + final int? _nativeValue = null; + const NavigationType_._internal(this._value); ///A link with an href attribute was activated by the user. - static const LINK_ACTIVATED = const NavigationType_._internal(0); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.linkActivated', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated', + value: 0), + EnumMacOSPlatform( + apiName: 'WKNavigationType.linkActivated', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated', + value: 0), + EnumWindowsPlatform(value: 0), + ]) + static const LINK_ACTIVATED = + const NavigationType_._internal('LINK_ACTIVATED'); ///A form was submitted. - static const FORM_SUBMITTED = const NavigationType_._internal(1); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 1), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 1), + ]) + static const FORM_SUBMITTED = + const NavigationType_._internal('FORM_SUBMITTED'); ///An item from the back-forward list was requested. - static const BACK_FORWARD = const NavigationType_._internal(2); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 2), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 2), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind', + value: 1), + ]) + static const BACK_FORWARD = const NavigationType_._internal('BACK_FORWARD'); ///The webpage was reloaded. - static const RELOAD = const NavigationType_._internal(3); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.reload', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/reload', + value: 3), + EnumMacOSPlatform( + apiName: 'WKNavigationType.reload', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/reload', + value: 3), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_NAVIGATION_KIND_RELOAD', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind', + value: 2), + ]) + static const RELOAD = const NavigationType_._internal('RELOAD'); ///A form was resubmitted (for example by going back, going forward, or reloading). - static const FORM_RESUBMITTED = const NavigationType_._internal(4); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted', + value: 4), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted', + value: 4), + ]) + static const FORM_RESUBMITTED = + const NavigationType_._internal('FORM_RESUBMITTED'); ///Navigation is taking place for some other reason. - static const OTHER = const NavigationType_._internal(-1); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.other', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/other', + value: -1), + EnumMacOSPlatform( + apiName: 'WKNavigationType.other', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/other', + value: -1), + EnumWindowsPlatform(value: 3), + ]) + static const OTHER = const NavigationType_._internal('OTHER'); } ///Class that represents the type of action triggering a navigation on iOS for the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart index bdf1b5258..4954e7c7b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart @@ -8,31 +8,131 @@ part of 'navigation_type.dart'; ///Class that represents the type of action triggering a navigation for the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event. class NavigationType { - final int _value; - final int _nativeValue; + final String _value; + final int? _nativeValue; const NavigationType._internal(this._value, this._nativeValue); // ignore: unused_element factory NavigationType._internalMultiPlatform( - int value, Function nativeValue) => + String value, Function nativeValue) => NavigationType._internal(value, nativeValue()); ///An item from the back-forward list was requested. - static const BACK_FORWARD = NavigationType._internal(2, 2); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- Windows ([Official API - COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind)) + static final BACK_FORWARD = + NavigationType._internalMultiPlatform('BACK_FORWARD', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 2; + case TargetPlatform.macOS: + return 2; + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); ///A form was resubmitted (for example by going back, going forward, or reloading). - static const FORM_RESUBMITTED = NavigationType._internal(4, 4); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted)) + static final FORM_RESUBMITTED = + NavigationType._internalMultiPlatform('FORM_RESUBMITTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 4; + case TargetPlatform.macOS: + return 4; + default: + break; + } + return null; + }); ///A form was submitted. - static const FORM_SUBMITTED = NavigationType._internal(1, 1); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + static final FORM_SUBMITTED = + NavigationType._internalMultiPlatform('FORM_SUBMITTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 1; + case TargetPlatform.macOS: + return 1; + default: + break; + } + return null; + }); ///A link with an href attribute was activated by the user. - static const LINK_ACTIVATED = NavigationType._internal(0, 0); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.linkActivated](https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated)) + ///- MacOS ([Official API - WKNavigationType.linkActivated](https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated)) + ///- Windows + static final LINK_ACTIVATED = + NavigationType._internalMultiPlatform('LINK_ACTIVATED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 0; + case TargetPlatform.macOS: + return 0; + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); ///Navigation is taking place for some other reason. - static const OTHER = NavigationType._internal(-1, -1); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.other](https://developer.apple.com/documentation/webkit/wknavigationtype/other)) + ///- MacOS ([Official API - WKNavigationType.other](https://developer.apple.com/documentation/webkit/wknavigationtype/other)) + ///- Windows + static final OTHER = NavigationType._internalMultiPlatform('OTHER', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return -1; + case TargetPlatform.macOS: + return -1; + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); ///The webpage was reloaded. - static const RELOAD = NavigationType._internal(3, 3); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.reload](https://developer.apple.com/documentation/webkit/wknavigationtype/reload)) + ///- MacOS ([Official API - WKNavigationType.reload](https://developer.apple.com/documentation/webkit/wknavigationtype/reload)) + ///- Windows ([Official API - COREWEBVIEW2_NAVIGATION_KIND_RELOAD](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind)) + static final RELOAD = NavigationType._internalMultiPlatform('RELOAD', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 3; + case TargetPlatform.macOS: + return 3; + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); ///Set of all values of [NavigationType]. static final Set values = [ @@ -44,8 +144,8 @@ class NavigationType { NavigationType.RELOAD, ].toSet(); - ///Gets a possible [NavigationType] instance from [int] value. - static NavigationType? fromValue(int? value) { + ///Gets a possible [NavigationType] instance from [String] value. + static NavigationType? fromValue(String? value) { if (value != null) { try { return NavigationType.values @@ -70,36 +170,70 @@ class NavigationType { return null; } - ///Gets [int] value. - int toValue() => _value; - - ///Gets [int] native value. - int toNativeValue() => _nativeValue; - - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; + /// Gets a possible [NavigationType] instance value with name [name]. + /// + /// Goes through [NavigationType.values] looking for a value with + /// name [name], as reported by [NavigationType.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationType? byName(String? name) { + if (name != null) { + try { + return NavigationType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } - @override - String toString() { + /// Creates a map from the names of [NavigationType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in NavigationType.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [int?] native value. + int? toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { switch (_value) { - case 2: + case 'BACK_FORWARD': return 'BACK_FORWARD'; - case 4: + case 'FORM_RESUBMITTED': return 'FORM_RESUBMITTED'; - case 1: + case 'FORM_SUBMITTED': return 'FORM_SUBMITTED'; - case 0: + case 'LINK_ACTIVATED': return 'LINK_ACTIVATED'; - case -1: + case 'OTHER': return 'OTHER'; - case 3: + case 'RELOAD': return 'RELOAD'; } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } } ///Class that represents the type of action triggering a navigation on iOS for the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event. @@ -168,20 +302,44 @@ class IOSWKNavigationType { return null; } + /// Gets a possible [IOSWKNavigationType] instance value with name [name]. + /// + /// Goes through [IOSWKNavigationType.values] looking for a value with + /// name [name], as reported by [IOSWKNavigationType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKNavigationType? byName(String? name) { + if (name != null) { + try { + return IOSWKNavigationType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKNavigationType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKNavigationType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 2: return 'BACK_FORWARD'; @@ -198,4 +356,15 @@ class IOSWKNavigationType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart index 7e4b5e432..228e28b4f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart @@ -59,20 +59,43 @@ class OverScrollMode { return null; } + /// Gets a possible [OverScrollMode] instance value with name [name]. + /// + /// Goes through [OverScrollMode.values] looking for a value with + /// name [name], as reported by [OverScrollMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static OverScrollMode? byName(String? name) { + if (name != null) { + try { + return OverScrollMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [OverScrollMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in OverScrollMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'ALWAYS'; @@ -83,6 +106,17 @@ class OverScrollMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the `WebView`'s over-scroll mode. @@ -141,20 +175,44 @@ class AndroidOverScrollMode { return null; } + /// Gets a possible [AndroidOverScrollMode] instance value with name [name]. + /// + /// Goes through [AndroidOverScrollMode.values] looking for a value with + /// name [name], as reported by [AndroidOverScrollMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidOverScrollMode? byName(String? name) { + if (name != null) { + try { + return AndroidOverScrollMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidOverScrollMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidOverScrollMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'OVER_SCROLL_ALWAYS'; @@ -165,4 +223,15 @@ class AndroidOverScrollMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart index 20e52f937..e807960ec 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'in_app_webview_rect.dart'; +import 'enum_method.dart'; part 'pdf_configuration.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart index b4c069453..ed54234d3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart @@ -15,20 +15,22 @@ class PDFConfiguration { PDFConfiguration({this.rect}); ///Gets a possible [PDFConfiguration] instance from a [Map] value. - static PDFConfiguration? fromMap(Map? map) { + static PDFConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PDFConfiguration( - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), }; } @@ -57,20 +59,22 @@ class IOSWKPDFConfiguration { IOSWKPDFConfiguration({this.rect}); ///Gets a possible [IOSWKPDFConfiguration] instance from a [Map] value. - static IOSWKPDFConfiguration? fromMap(Map? map) { + static IOSWKPDFConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKPDFConfiguration( - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart new file mode 100644 index 000000000..c6f346e4e --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart @@ -0,0 +1,53 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'pdf_toolbar_items.g.dart'; + +///Class used to customize the PDF toolbar items. +@ExchangeableEnum(bitwiseOrOperator: true) +class PdfToolbarItems_ { + // ignore: unused_field + final int _value; + const PdfToolbarItems_._internal(this._value); + + ///No item. + static const NONE = const PdfToolbarItems_._internal(0); + + ///The save button. + static const SAVE = const PdfToolbarItems_._internal(1); + + ///The print button. + static const PRINT = const PdfToolbarItems_._internal(2); + + ///The save as button. + static const SAVE_AS = const PdfToolbarItems_._internal(4); + + ///The zoom in button. + static const ZOOM_IN = const PdfToolbarItems_._internal(8); + + ///The zoom out button. + static const ZOOM_OUT = const PdfToolbarItems_._internal(16); + + ///The rotate button. + static const ROTATE = const PdfToolbarItems_._internal(32); + + ///The fit page button. + static const FIT_PAGE = const PdfToolbarItems_._internal(64); + + ///The page layout button. + static const PAGE_LAYOUT = const PdfToolbarItems_._internal(128); + + ///The bookmarks button. + static const BOOKMARKS = const PdfToolbarItems_._internal(256); + + ///The page select button. + static const PAGE_SELECTOR = const PdfToolbarItems_._internal(512); + + ///The search button. + static const SEARCH = const PdfToolbarItems_._internal(1024); + + ///The full screen button. + static const FULL_SCREEN = const PdfToolbarItems_._internal(2048); + + ///The more settings button. + static const MORE_SETTINGS = const PdfToolbarItems_._internal(4096); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart new file mode 100644 index 000000000..5a9fdc1e5 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart @@ -0,0 +1,188 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pdf_toolbar_items.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to customize the PDF toolbar items. +class PdfToolbarItems { + final int _value; + final int _nativeValue; + const PdfToolbarItems._internal(this._value, this._nativeValue); +// ignore: unused_element + factory PdfToolbarItems._internalMultiPlatform( + int value, Function nativeValue) => + PdfToolbarItems._internal(value, nativeValue()); + + ///The bookmarks button. + static const BOOKMARKS = PdfToolbarItems._internal(256, 256); + + ///The fit page button. + static const FIT_PAGE = PdfToolbarItems._internal(64, 64); + + ///The full screen button. + static const FULL_SCREEN = PdfToolbarItems._internal(2048, 2048); + + ///The more settings button. + static const MORE_SETTINGS = PdfToolbarItems._internal(4096, 4096); + + ///No item. + static const NONE = PdfToolbarItems._internal(0, 0); + + ///The page layout button. + static const PAGE_LAYOUT = PdfToolbarItems._internal(128, 128); + + ///The page select button. + static const PAGE_SELECTOR = PdfToolbarItems._internal(512, 512); + + ///The print button. + static const PRINT = PdfToolbarItems._internal(2, 2); + + ///The rotate button. + static const ROTATE = PdfToolbarItems._internal(32, 32); + + ///The save button. + static const SAVE = PdfToolbarItems._internal(1, 1); + + ///The save as button. + static const SAVE_AS = PdfToolbarItems._internal(4, 4); + + ///The search button. + static const SEARCH = PdfToolbarItems._internal(1024, 1024); + + ///The zoom in button. + static const ZOOM_IN = PdfToolbarItems._internal(8, 8); + + ///The zoom out button. + static const ZOOM_OUT = PdfToolbarItems._internal(16, 16); + + ///Set of all values of [PdfToolbarItems]. + static final Set values = [ + PdfToolbarItems.BOOKMARKS, + PdfToolbarItems.FIT_PAGE, + PdfToolbarItems.FULL_SCREEN, + PdfToolbarItems.MORE_SETTINGS, + PdfToolbarItems.NONE, + PdfToolbarItems.PAGE_LAYOUT, + PdfToolbarItems.PAGE_SELECTOR, + PdfToolbarItems.PRINT, + PdfToolbarItems.ROTATE, + PdfToolbarItems.SAVE, + PdfToolbarItems.SAVE_AS, + PdfToolbarItems.SEARCH, + PdfToolbarItems.ZOOM_IN, + PdfToolbarItems.ZOOM_OUT, + ].toSet(); + + ///Gets a possible [PdfToolbarItems] instance from [int] value. + static PdfToolbarItems? fromValue(int? value) { + if (value != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return PdfToolbarItems._internal(value, value); + } + } + return null; + } + + ///Gets a possible [PdfToolbarItems] instance from a native value. + static PdfToolbarItems? fromNativeValue(int? value) { + if (value != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return PdfToolbarItems._internal(value, value); + } + } + return null; + } + + /// Gets a possible [PdfToolbarItems] instance value with name [name]. + /// + /// Goes through [PdfToolbarItems.values] looking for a value with + /// name [name], as reported by [PdfToolbarItems.name]. + /// Returns the first value with the given name, otherwise `null`. + static PdfToolbarItems? byName(String? name) { + if (name != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PdfToolbarItems] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in PdfToolbarItems.values) value.name(): value + }; + + ///Gets [int] value. + int toValue() => _value; + + ///Gets [int] native value. + int toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 256: + return 'BOOKMARKS'; + case 64: + return 'FIT_PAGE'; + case 2048: + return 'FULL_SCREEN'; + case 4096: + return 'MORE_SETTINGS'; + case 0: + return 'NONE'; + case 128: + return 'PAGE_LAYOUT'; + case 512: + return 'PAGE_SELECTOR'; + case 2: + return 'PRINT'; + case 32: + return 'ROTATE'; + case 1: + return 'SAVE'; + case 4: + return 'SAVE_AS'; + case 1024: + return 'SEARCH'; + case 8: + return 'ZOOM_IN'; + case 16: + return 'ZOOM_OUT'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + PdfToolbarItems operator |(PdfToolbarItems value) => + PdfToolbarItems._internal( + value.toValue() | _value, value.toNativeValue() | _nativeValue); + @override + String toString() { + return name(); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart index acb775ce9..dc0ca59cf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart @@ -1,9 +1,11 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; import 'permission_resource_type.dart'; import 'permission_response.dart'; import 'frame_info.dart'; +import 'enum_method.dart'; part 'permission_request.g.dart'; @@ -15,7 +17,7 @@ class PermissionRequest_ { ///List of resources the web content wants to access. /// - ///**NOTE for iOS**: this list will have only 1 element and will be used by the [PermissionResponse.action] + ///**NOTE for iOS, macOS and Windows**: this list will have only 1 element and will be used by the [PermissionResponse.action] ///as the resource to consider when applying the corresponding action. List resources; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart index d225a7a4c..d2061f1c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart @@ -16,32 +16,47 @@ class PermissionRequest { ///List of resources the web content wants to access. /// - ///**NOTE for iOS**: this list will have only 1 element and will be used by the [PermissionResponse.action] + ///**NOTE for iOS, macOS and Windows**: this list will have only 1 element and will be used by the [PermissionResponse.action] ///as the resource to consider when applying the corresponding action. List resources; PermissionRequest( {this.frame, required this.origin, this.resources = const []}); ///Gets a possible [PermissionRequest] instance from a [Map] value. - static PermissionRequest? fromMap(Map? map) { + static PermissionRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionRequest( - frame: FrameInfo.fromMap(map['frame']?.cast()), + frame: FrameInfo.fromMap(map['frame']?.cast(), + enumMethod: enumMethod), origin: WebUri(map['origin']), ); - instance.resources = List.from(map['resources'] - .map((e) => PermissionResourceType.fromNativeValue(e)!)); + if (map['resources'] != null) { + instance.resources = List.from(map['resources'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResourceType.fromNativeValue(e), + EnumMethod.value => PermissionResourceType.fromValue(e), + EnumMethod.name => PermissionResourceType.byName(e) + }!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "frame": frame?.toMap(), + "frame": frame?.toMap(enumMethod: enumMethod), "origin": origin.toString(), - "resources": resources.map((e) => e.toNativeValue()).toList(), + "resources": resources + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.dart index 4c5837cce..527f5fa5c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.dart @@ -30,6 +30,11 @@ class PermissionResourceType_ { apiName: 'WKMediaCaptureType.microphone', apiUrl: 'https://developer.apple.com/documentation/webkit/wkmediacapturetype/microphone', + value: 1), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_MICROPHONE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', value: 1) ]) static const MICROPHONE = PermissionResourceType_._internal('MICROPHONE'); @@ -43,6 +48,11 @@ class PermissionResourceType_ { apiUrl: 'https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_MIDI_SYSEX', value: 'android.webkit.resource.MIDI_SYSEX'), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_MIDI_SYSTEM_EXCLUSIVE_MESSAGES', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 11) ]) static const MIDI_SYSEX = PermissionResourceType_._internal('MIDI_SYSEX'); @@ -75,7 +85,12 @@ class PermissionResourceType_ { apiName: 'WKMediaCaptureType.camera', apiUrl: 'https://developer.apple.com/documentation/webkit/wkmediacapturetype/camera', - value: 0) + value: 0), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_CAMERA', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 2) ]) static const CAMERA = PermissionResourceType_._internal('CAMERA'); @@ -104,4 +119,122 @@ class PermissionResourceType_ { ]) static const DEVICE_ORIENTATION_AND_MOTION = PermissionResourceType_._internal('DEVICE_ORIENTATION_AND_MOTION'); + + ///Indicates an unknown permission. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_UNKNOWN_PERMISSION', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 0) + ]) + static const UNKNOWN = PermissionResourceType_._internal('UNKNOWN'); + + ///Indicates permission to access geolocation. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 3) + ]) + static const GEOLOCATION = PermissionResourceType_._internal('GEOLOCATION'); + + ///Indicates permission to send web notifications. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 4) + ]) + static const NOTIFICATIONS = + PermissionResourceType_._internal('NOTIFICATIONS'); + + ///Indicates permission to access generic sensor. Generic Sensor covers ambient-light-sensor, accelerometer, gyroscope, and magnetometer. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 5) + ]) + static const OTHER_SENSORS = + PermissionResourceType_._internal('OTHER_SENSORS'); + + ///Indicates permission to read the system clipboard without a user gesture. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 6) + ]) + static const CLIPBOARD_READ = + PermissionResourceType_._internal('CLIPBOARD_READ'); + + ///Indicates permission to automatically download multiple files. + ///Permission is requested when multiple downloads are triggered in quick succession. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_MULTIPLE_AUTOMATIC_DOWNLOADS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 7) + ]) + static const MULTIPLE_AUTOMATIC_DOWNLOADS = + PermissionResourceType_._internal('MULTIPLE_AUTOMATIC_DOWNLOADS'); + + ///Indicates permission to read and write to files or folders on the device. + ///Permission is requested when developers use the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) + ///to show the file or folder picker to the end user, and then request + ///"readwrite" permission for the user's selection. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_FILE_READ_WRITE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 8) + ]) + static const FILE_READ_WRITE = + PermissionResourceType_._internal('FILE_READ_WRITE'); + + ///Indicates permission to play audio and video automatically on sites. + ///This permission affects the autoplay attribute and play method of the audio + ///and video HTML elements, and the start method of the Web Audio API. + ///See the [Autoplay guide for media and Web Audio APIs](https://developer.mozilla.org/docs/Web/Media/Autoplay_guide) + ///for details. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 9) + ]) + static const AUTOPLAY = PermissionResourceType_._internal('AUTOPLAY'); + + ///Indicates permission to use fonts on the device. + ///Permission is requested when developers use the [Local Font Access API](https://wicg.github.io/local-font-access/) + ///to query the system fonts available for styling web content. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 10) + ]) + static const LOCAL_FONTS = PermissionResourceType_._internal('LOCAL_FONTS'); + + ///Indicates permission to open and place windows on the screen. + ///Permission is requested when developers use the [Multi-Screen Window Placement API](https://www.w3.org/TR/window-placement/) + ///to get screen details. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PERMISSION_KIND_WINDOW_MANAGEMENT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind', + value: 12) + ]) + static const WINDOW_MANAGEMENT = + PermissionResourceType_._internal('WINDOW_MANAGEMENT'); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart index 3a3a702ff..3ab4ae8a1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart @@ -16,12 +16,32 @@ class PermissionResourceType { String value, Function nativeValue) => PermissionResourceType._internal(value, nativeValue()); + ///Indicates permission to play audio and video automatically on sites. + ///This permission affects the autoplay attribute and play method of the audio + ///and video HTML elements, and the start method of the Web Audio API. + ///See the [Autoplay guide for media and Web Audio APIs](https://developer.mozilla.org/docs/Web/Media/Autoplay_guide) + ///for details. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final AUTOPLAY = + PermissionResourceType._internalMultiPlatform('AUTOPLAY', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 9; + default: + break; + } + return null; + }); + ///Resource belongs to video capture device, like camera. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - PermissionRequest.RESOURCE_VIDEO_CAPTURE](https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_VIDEO_CAPTURE)) ///- iOS 15.0+ ([Official API - WKMediaCaptureType.camera](https://developer.apple.com/documentation/webkit/wkmediacapturetype/camera)) ///- MacOS 12.0+ ([Official API - WKMediaCaptureType.camera](https://developer.apple.com/documentation/webkit/wkmediacapturetype/camera)) + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_CAMERA](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) static final CAMERA = PermissionResourceType._internalMultiPlatform('CAMERA', () { switch (defaultTargetPlatform) { @@ -31,6 +51,8 @@ class PermissionResourceType { return 0; case TargetPlatform.macOS: return 0; + case TargetPlatform.windows: + return 2; default: break; } @@ -56,6 +78,21 @@ class PermissionResourceType { return null; }); + ///Indicates permission to read the system clipboard without a user gesture. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final CLIPBOARD_READ = + PermissionResourceType._internalMultiPlatform('CLIPBOARD_READ', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 6; + default: + break; + } + return null; + }); + ///Resource belongs to the device’s orientation and motion. /// ///**Officially Supported Platforms/Implementations**: @@ -75,12 +112,63 @@ class PermissionResourceType { return null; }); + ///Indicates permission to read and write to files or folders on the device. + ///Permission is requested when developers use the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) + ///to show the file or folder picker to the end user, and then request + ///"readwrite" permission for the user's selection. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_FILE_READ_WRITE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final FILE_READ_WRITE = + PermissionResourceType._internalMultiPlatform('FILE_READ_WRITE', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 8; + default: + break; + } + return null; + }); + + ///Indicates permission to access geolocation. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final GEOLOCATION = + PermissionResourceType._internalMultiPlatform('GEOLOCATION', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///Indicates permission to use fonts on the device. + ///Permission is requested when developers use the [Local Font Access API](https://wicg.github.io/local-font-access/) + ///to query the system fonts available for styling web content. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final LOCAL_FONTS = + PermissionResourceType._internalMultiPlatform('LOCAL_FONTS', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 10; + default: + break; + } + return null; + }); + ///Resource belongs to audio capture device, like microphone. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - PermissionRequest.RESOURCE_AUDIO_CAPTURE](https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_AUDIO_CAPTURE)) ///- iOS 15.0+ ([Official API - WKMediaCaptureType.microphone](https://developer.apple.com/documentation/webkit/wkmediacapturetype/microphone)) ///- MacOS 12.0+ ([Official API - WKMediaCaptureType.microphone](https://developer.apple.com/documentation/webkit/wkmediacapturetype/microphone)) + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_MICROPHONE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) static final MICROPHONE = PermissionResourceType._internalMultiPlatform('MICROPHONE', () { switch (defaultTargetPlatform) { @@ -90,6 +178,8 @@ class PermissionResourceType { return 1; case TargetPlatform.macOS: return 1; + case TargetPlatform.windows: + return 1; default: break; } @@ -102,11 +192,61 @@ class PermissionResourceType { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - PermissionRequest.RESOURCE_MIDI_SYSEX](https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_MIDI_SYSEX)) + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_MIDI_SYSTEM_EXCLUSIVE_MESSAGES](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) static final MIDI_SYSEX = PermissionResourceType._internalMultiPlatform('MIDI_SYSEX', () { switch (defaultTargetPlatform) { case TargetPlatform.android: return 'android.webkit.resource.MIDI_SYSEX'; + case TargetPlatform.windows: + return 11; + default: + break; + } + return null; + }); + + ///Indicates permission to automatically download multiple files. + ///Permission is requested when multiple downloads are triggered in quick succession. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_MULTIPLE_AUTOMATIC_DOWNLOADS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final MULTIPLE_AUTOMATIC_DOWNLOADS = + PermissionResourceType._internalMultiPlatform( + 'MULTIPLE_AUTOMATIC_DOWNLOADS', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 7; + default: + break; + } + return null; + }); + + ///Indicates permission to send web notifications. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final NOTIFICATIONS = + PermissionResourceType._internalMultiPlatform('NOTIFICATIONS', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///Indicates permission to access generic sensor. Generic Sensor covers ambient-light-sensor, accelerometer, gyroscope, and magnetometer. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final OTHER_SENSORS = + PermissionResourceType._internalMultiPlatform('OTHER_SENSORS', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 5; default: break; } @@ -128,14 +268,56 @@ class PermissionResourceType { return null; }); + ///Indicates an unknown permission. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_UNKNOWN_PERMISSION](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final UNKNOWN = + PermissionResourceType._internalMultiPlatform('UNKNOWN', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///Indicates permission to open and place windows on the screen. + ///Permission is requested when developers use the [Multi-Screen Window Placement API](https://www.w3.org/TR/window-placement/) + ///to get screen details. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PERMISSION_KIND_WINDOW_MANAGEMENT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2792.45#corewebview2_permission_kind)) + static final WINDOW_MANAGEMENT = + PermissionResourceType._internalMultiPlatform('WINDOW_MANAGEMENT', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 12; + default: + break; + } + return null; + }); + ///Set of all values of [PermissionResourceType]. static final Set values = [ + PermissionResourceType.AUTOPLAY, PermissionResourceType.CAMERA, PermissionResourceType.CAMERA_AND_MICROPHONE, + PermissionResourceType.CLIPBOARD_READ, PermissionResourceType.DEVICE_ORIENTATION_AND_MOTION, + PermissionResourceType.FILE_READ_WRITE, + PermissionResourceType.GEOLOCATION, + PermissionResourceType.LOCAL_FONTS, PermissionResourceType.MICROPHONE, PermissionResourceType.MIDI_SYSEX, + PermissionResourceType.MULTIPLE_AUTOMATIC_DOWNLOADS, + PermissionResourceType.NOTIFICATIONS, + PermissionResourceType.OTHER_SENSORS, PermissionResourceType.PROTECTED_MEDIA_ID, + PermissionResourceType.UNKNOWN, + PermissionResourceType.WINDOW_MANAGEMENT, ].toSet(); ///Gets a possible [PermissionResourceType] instance from [String] value. @@ -164,12 +346,81 @@ class PermissionResourceType { return null; } + /// Gets a possible [PermissionResourceType] instance value with name [name]. + /// + /// Goes through [PermissionResourceType.values] looking for a value with + /// name [name], as reported by [PermissionResourceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionResourceType? byName(String? name) { + if (name != null) { + try { + return PermissionResourceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionResourceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionResourceType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [dynamic] native value. dynamic toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'AUTOPLAY': + return 'AUTOPLAY'; + case 'CAMERA': + return 'CAMERA'; + case 'CAMERA_AND_MICROPHONE': + return 'CAMERA_AND_MICROPHONE'; + case 'CLIPBOARD_READ': + return 'CLIPBOARD_READ'; + case 'DEVICE_ORIENTATION_AND_MOTION': + return 'DEVICE_ORIENTATION_AND_MOTION'; + case 'FILE_READ_WRITE': + return 'FILE_READ_WRITE'; + case 'GEOLOCATION': + return 'GEOLOCATION'; + case 'LOCAL_FONTS': + return 'LOCAL_FONTS'; + case 'MICROPHONE': + return 'MICROPHONE'; + case 'MIDI_SYSEX': + return 'MIDI_SYSEX'; + case 'MULTIPLE_AUTOMATIC_DOWNLOADS': + return 'MULTIPLE_AUTOMATIC_DOWNLOADS'; + case 'NOTIFICATIONS': + return 'NOTIFICATIONS'; + case 'OTHER_SENSORS': + return 'OTHER_SENSORS'; + case 'PROTECTED_MEDIA_ID': + return 'PROTECTED_MEDIA_ID'; + case 'UNKNOWN': + return 'UNKNOWN'; + case 'WINDOW_MANAGEMENT': + return 'WINDOW_MANAGEMENT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart index 08608d684..39b7d6457 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart @@ -1,7 +1,9 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'permission_resource_type.dart'; import 'permission_response_action.dart'; +import 'enum_method.dart'; part 'permission_response.g.dart'; @@ -10,7 +12,7 @@ part 'permission_response.g.dart'; class PermissionResponse_ { ///Resources granted to be accessed by origin. /// - ///**NOTE for iOS**: not used. The [action] taken is based on the [PermissionRequest.resources]. + ///**NOTE for iOS, macOS and Windows**: not used. The [action] taken is based on the [PermissionRequest.resources]. List resources; ///Indicate the [PermissionResponseAction] to take in response of a permission request. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart index a7f917a0e..296ae8548 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart @@ -13,28 +13,51 @@ class PermissionResponse { ///Resources granted to be accessed by origin. /// - ///**NOTE for iOS**: not used. The [action] taken is based on the [PermissionRequest.resources]. + ///**NOTE for iOS, macOS and Windows**: not used. The [action] taken is based on the [PermissionRequest.resources]. List resources; PermissionResponse( {this.action = PermissionResponseAction.DENY, this.resources = const []}); ///Gets a possible [PermissionResponse] instance from a [Map] value. - static PermissionResponse? fromMap(Map? map) { + static PermissionResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionResponse(); - instance.action = PermissionResponseAction.fromNativeValue(map['action']); - instance.resources = List.from(map['resources'] - .map((e) => PermissionResourceType.fromNativeValue(e)!)); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResponseAction.fromNativeValue(map['action']), + EnumMethod.value => PermissionResponseAction.fromValue(map['action']), + EnumMethod.name => PermissionResponseAction.byName(map['action']) + }; + if (map['resources'] != null) { + instance.resources = List.from(map['resources'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResourceType.fromNativeValue(e), + EnumMethod.value => PermissionResourceType.fromValue(e), + EnumMethod.name => PermissionResourceType.byName(e) + }!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), - "resources": resources.map((e) => e.toNativeValue()).toList(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, + "resources": resources + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), }; } @@ -63,21 +86,33 @@ class PermissionRequestResponse { this.resources = const []}); ///Gets a possible [PermissionRequestResponse] instance from a [Map] value. - static PermissionRequestResponse? fromMap(Map? map) { + static PermissionRequestResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionRequestResponse(); - instance.action = - PermissionRequestResponseAction.fromNativeValue(map['action']); - instance.resources = List.from(map['resources']!.cast()); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionRequestResponseAction.fromNativeValue(map['action']), + EnumMethod.value => + PermissionRequestResponseAction.fromValue(map['action']), + EnumMethod.name => PermissionRequestResponseAction.byName(map['action']) + }; + if (map['resources'] != null) { + instance.resources = List.from(map['resources']!.cast()); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "resources": resources, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart index 33c5e4964..2dbce8e18 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart @@ -58,20 +58,44 @@ class PermissionResponseAction { return null; } + /// Gets a possible [PermissionResponseAction] instance value with name [name]. + /// + /// Goes through [PermissionResponseAction.values] looking for a value with + /// name [name], as reported by [PermissionResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionResponseAction? byName(String? name) { + if (name != null) { + try { + return PermissionResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'DENY'; @@ -82,6 +106,17 @@ class PermissionResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class used by [PermissionRequestResponse] class. @@ -135,12 +170,54 @@ class PermissionRequestResponseAction { return null; } + /// Gets a possible [PermissionRequestResponseAction] instance value with name [name]. + /// + /// Goes through [PermissionRequestResponseAction.values] looking for a value with + /// name [name], as reported by [PermissionRequestResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionRequestResponseAction? byName(String? name) { + if (name != null) { + try { + return PermissionRequestResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionRequestResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionRequestResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'DENY'; + case 1: + return 'GRANT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -149,12 +226,6 @@ class PermissionRequestResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'DENY'; - case 1: - return 'GRANT'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart new file mode 100644 index 000000000..4177c4ab0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart @@ -0,0 +1,37 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'enum_method.dart'; + +part 'physical_key_status.g.dart'; + +///Contains the information packed into the LPARAM sent to a Win32 key event. +@ExchangeableObject() +class PhysicalKeyStatus_ { + ///Indicates that the key is an extended key. + bool isExtendedKey; + + ///Indicates that the key was released. + bool isKeyReleased; + + ///Indicates that a menu key is held down (context code). + bool isMenuKeyDown; + + ///Specifies the repeat count for the current message. + int repeatCount; + + ///Specifies the scan code. + int scanCode; + + ///Indicates that the key was held down. + bool wasKeyDown; + + @ExchangeableObjectConstructor() + PhysicalKeyStatus_({ + required this.isExtendedKey, + required this.isKeyReleased, + required this.isMenuKeyDown, + required this.repeatCount, + required this.scanCode, + required this.wasKeyDown, + }); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart new file mode 100644 index 000000000..a655c26e0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart @@ -0,0 +1,74 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'physical_key_status.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Contains the information packed into the LPARAM sent to a Win32 key event. +class PhysicalKeyStatus { + ///Indicates that the key is an extended key. + bool isExtendedKey; + + ///Indicates that the key was released. + bool isKeyReleased; + + ///Indicates that a menu key is held down (context code). + bool isMenuKeyDown; + + ///Specifies the repeat count for the current message. + int repeatCount; + + ///Specifies the scan code. + int scanCode; + + ///Indicates that the key was held down. + bool wasKeyDown; + PhysicalKeyStatus( + {required this.isExtendedKey, + required this.isKeyReleased, + required this.isMenuKeyDown, + required this.repeatCount, + required this.scanCode, + required this.wasKeyDown}); + + ///Gets a possible [PhysicalKeyStatus] instance from a [Map] value. + static PhysicalKeyStatus? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = PhysicalKeyStatus( + isExtendedKey: map['isExtendedKey'], + isKeyReleased: map['isKeyReleased'], + isMenuKeyDown: map['isMenuKeyDown'], + repeatCount: map['repeatCount'], + scanCode: map['scanCode'], + wasKeyDown: map['wasKeyDown'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "isExtendedKey": isExtendedKey, + "isKeyReleased": isKeyReleased, + "isMenuKeyDown": isMenuKeyDown, + "repeatCount": repeatCount, + "scanCode": scanCode, + "wasKeyDown": wasKeyDown, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'PhysicalKeyStatus{isExtendedKey: $isExtendedKey, isKeyReleased: $isKeyReleased, isMenuKeyDown: $isMenuKeyDown, repeatCount: $repeatCount, scanCode: $scanCode, wasKeyDown: $wasKeyDown}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart index 583ba2b94..cc460b81c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../chrome_safari_browser/platform_chrome_safari_browser.dart'; +import 'enum_method.dart'; part 'prewarming_token.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart index 334482b8b..bec21040d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart @@ -13,7 +13,8 @@ class PrewarmingToken { PrewarmingToken({required this.id}); ///Gets a possible [PrewarmingToken] instance from a [Map] value. - static PrewarmingToken? fromMap(Map? map) { + static PrewarmingToken? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -24,7 +25,7 @@ class PrewarmingToken { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart index db5c09244..c108314e1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart @@ -1,17 +1,18 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; -import '../util.dart'; import '../print_job/main.dart'; +import '../util.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; import 'in_app_webview_rect.dart'; import 'print_job_color_mode.dart'; +import 'print_job_disposition.dart'; import 'print_job_duplex_mode.dart'; -import 'print_job_orientation.dart'; import 'print_job_media_size.dart'; -import 'print_job_resolution.dart'; +import 'print_job_orientation.dart'; import 'print_job_pagination_mode.dart'; -import 'print_job_disposition.dart'; +import 'print_job_resolution.dart'; part 'print_job_attributes.g.dart'; @@ -32,6 +33,11 @@ class PrintJobAttributes_ { PrintJobDuplexMode_? duplex; ///The orientation of the printed content, portrait or landscape. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + ]) PrintJobOrientation_? orientation; ///The media size. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart index 6c9a99cf3..90385bd45 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart @@ -145,6 +145,11 @@ class PrintJobAttributes { bool? mustCollate; ///The orientation of the printed content, portrait or landscape. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS PrintJobOrientation? orientation; ///The number of logical pages to be tiled horizontally on a physical sheet of paper. @@ -240,84 +245,145 @@ class PrintJobAttributes { this.verticalPagination}); ///Gets a possible [PrintJobAttributes] instance from a [Map] value. - static PrintJobAttributes? fromMap(Map? map) { + static PrintJobAttributes? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PrintJobAttributes( - colorMode: PrintJobColorMode.fromNativeValue(map['colorMode']), + colorMode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobColorMode.fromNativeValue(map['colorMode']), + EnumMethod.value => PrintJobColorMode.fromValue(map['colorMode']), + EnumMethod.name => PrintJobColorMode.byName(map['colorMode']) + }, detailedErrorReporting: map['detailedErrorReporting'], - duplex: PrintJobDuplexMode.fromNativeValue(map['duplex']), + duplex: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobDuplexMode.fromNativeValue(map['duplex']), + EnumMethod.value => PrintJobDuplexMode.fromValue(map['duplex']), + EnumMethod.name => PrintJobDuplexMode.byName(map['duplex']) + }, faxNumber: map['faxNumber'], footerHeight: map['footerHeight'], headerAndFooter: map['headerAndFooter'], headerHeight: map['headerHeight'], - horizontalPagination: + horizontalPagination: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobPaginationMode.fromNativeValue(map['horizontalPagination']), + EnumMethod.value => + PrintJobPaginationMode.fromValue(map['horizontalPagination']), + EnumMethod.name => + PrintJobPaginationMode.byName(map['horizontalPagination']) + }, isHorizontallyCentered: map['isHorizontallyCentered'], isSelectionOnly: map['isSelectionOnly'], isVerticallyCentered: map['isVerticallyCentered'], - jobDisposition: + jobDisposition: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobDisposition.fromNativeValue(map['jobDisposition']), + EnumMethod.value => + PrintJobDisposition.fromValue(map['jobDisposition']), + EnumMethod.name => PrintJobDisposition.byName(map['jobDisposition']) + }, jobSavingURL: map['jobSavingURL'] != null ? WebUri(map['jobSavingURL']) : null, localizedPaperName: map['localizedPaperName'], margins: MapEdgeInsets.fromMap(map['margins']?.cast()), maximumContentHeight: map['maximumContentHeight'], maximumContentWidth: map['maximumContentWidth'], - mediaSize: - PrintJobMediaSize.fromMap(map['mediaSize']?.cast()), + mediaSize: PrintJobMediaSize.fromMap( + map['mediaSize']?.cast(), + enumMethod: enumMethod), mustCollate: map['mustCollate'], - orientation: PrintJobOrientation.fromNativeValue(map['orientation']), + orientation: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobOrientation.fromNativeValue(map['orientation']), + EnumMethod.value => PrintJobOrientation.fromValue(map['orientation']), + EnumMethod.name => PrintJobOrientation.byName(map['orientation']) + }, pagesAcross: map['pagesAcross'], pagesDown: map['pagesDown'], paperName: map['paperName'], - paperRect: - InAppWebViewRect.fromMap(map['paperRect']?.cast()), + paperRect: InAppWebViewRect.fromMap( + map['paperRect']?.cast(), + enumMethod: enumMethod), printableRect: InAppWebViewRect.fromMap( - map['printableRect']?.cast()), + map['printableRect']?.cast(), + enumMethod: enumMethod), resolution: PrintJobResolution.fromMap( - map['resolution']?.cast()), + map['resolution']?.cast(), + enumMethod: enumMethod), scalingFactor: map['scalingFactor'], time: map['time'], - verticalPagination: + verticalPagination: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobPaginationMode.fromNativeValue(map['verticalPagination']), + EnumMethod.value => + PrintJobPaginationMode.fromValue(map['verticalPagination']), + EnumMethod.name => + PrintJobPaginationMode.byName(map['verticalPagination']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "colorMode": colorMode?.toNativeValue(), + "colorMode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => colorMode?.toNativeValue(), + EnumMethod.value => colorMode?.toValue(), + EnumMethod.name => colorMode?.name() + }, "detailedErrorReporting": detailedErrorReporting, - "duplex": duplex?.toNativeValue(), + "duplex": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => duplex?.toNativeValue(), + EnumMethod.value => duplex?.toValue(), + EnumMethod.name => duplex?.name() + }, "faxNumber": faxNumber, "footerHeight": footerHeight, "headerAndFooter": headerAndFooter, "headerHeight": headerHeight, - "horizontalPagination": horizontalPagination?.toNativeValue(), + "horizontalPagination": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => horizontalPagination?.toNativeValue(), + EnumMethod.value => horizontalPagination?.toValue(), + EnumMethod.name => horizontalPagination?.name() + }, "isHorizontallyCentered": isHorizontallyCentered, "isSelectionOnly": isSelectionOnly, "isVerticallyCentered": isVerticallyCentered, - "jobDisposition": jobDisposition?.toNativeValue(), + "jobDisposition": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => jobDisposition?.toNativeValue(), + EnumMethod.value => jobDisposition?.toValue(), + EnumMethod.name => jobDisposition?.name() + }, "jobSavingURL": jobSavingURL?.toString(), "localizedPaperName": localizedPaperName, "margins": margins?.toMap(), "maximumContentHeight": maximumContentHeight, "maximumContentWidth": maximumContentWidth, - "mediaSize": mediaSize?.toMap(), + "mediaSize": mediaSize?.toMap(enumMethod: enumMethod), "mustCollate": mustCollate, - "orientation": orientation?.toNativeValue(), + "orientation": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => orientation?.toNativeValue(), + EnumMethod.value => orientation?.toValue(), + EnumMethod.name => orientation?.name() + }, "pagesAcross": pagesAcross, "pagesDown": pagesDown, "paperName": paperName, - "paperRect": paperRect?.toMap(), - "printableRect": printableRect?.toMap(), - "resolution": resolution?.toMap(), + "paperRect": paperRect?.toMap(enumMethod: enumMethod), + "printableRect": printableRect?.toMap(enumMethod: enumMethod), + "resolution": resolution?.toMap(enumMethod: enumMethod), "scalingFactor": scalingFactor, "time": time, - "verticalPagination": verticalPagination?.toNativeValue(), + "verticalPagination": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => verticalPagination?.toNativeValue(), + EnumMethod.value => verticalPagination?.toValue(), + EnumMethod.name => verticalPagination?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart index 4bd1478f0..471ec4619 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart @@ -82,12 +82,53 @@ class PrintJobColorMode { return null; } + /// Gets a possible [PrintJobColorMode] instance value with name [name]. + /// + /// Goes through [PrintJobColorMode.values] looking for a value with + /// name [name], as reported by [PrintJobColorMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobColorMode? byName(String? name) { + if (name != null) { + try { + return PrintJobColorMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobColorMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobColorMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [dynamic] native value. dynamic toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 2: + return 'COLOR'; + case 1: + return 'MONOCHROME'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -96,12 +137,6 @@ class PrintJobColorMode { @override String toString() { - switch (_value) { - case 2: - return 'COLOR'; - case 1: - return 'MONOCHROME'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart index 65f090465..14c5a01e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart @@ -108,12 +108,57 @@ class PrintJobDisposition { return null; } + /// Gets a possible [PrintJobDisposition] instance value with name [name]. + /// + /// Goes through [PrintJobDisposition.values] looking for a value with + /// name [name], as reported by [PrintJobDisposition.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobDisposition? byName(String? name) { + if (name != null) { + try { + return PrintJobDisposition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobDisposition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobDisposition.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CANCEL': + return 'CANCEL'; + case 'PREVIEW': + return 'PREVIEW'; + case 'SAVE': + return 'SAVE'; + case 'SPOOL': + return 'SPOOL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart index fb607beca..0ee6d2811 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart @@ -113,12 +113,55 @@ class PrintJobDuplexMode { return null; } + /// Gets a possible [PrintJobDuplexMode] instance value with name [name]. + /// + /// Goes through [PrintJobDuplexMode.values] looking for a value with + /// name [name], as reported by [PrintJobDuplexMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobDuplexMode? byName(String? name) { + if (name != null) { + try { + return PrintJobDuplexMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobDuplexMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobDuplexMode.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'LONG_EDGE': + return 'LONG_EDGE'; + case 'NONE': + return 'NONE'; + case 'SHORT_EDGE': + return 'SHORT_EDGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart index 6e9c16a69..eaf4fee13 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart @@ -6,6 +6,7 @@ import 'print_job_rendering_quality.dart'; import 'print_job_state.dart'; import 'print_job_page_order.dart'; import 'printer.dart'; +import 'enum_method.dart'; part 'print_job_info.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart index f65ecd164..c24a74ff8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart @@ -139,13 +139,15 @@ class PrintJobInfo { this.state}); ///Gets a possible [PrintJobInfo] instance from a [Map] value. - static PrintJobInfo? fromMap(Map? map) { + static PrintJobInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PrintJobInfo( attributes: PrintJobAttributes.fromMap( - map['attributes']?.cast()), + map['attributes']?.cast(), + enumMethod: enumMethod), canSpawnSeparateThread: map['canSpawnSeparateThread'], copies: map['copies'], creationTime: map['creationTime'], @@ -155,21 +157,37 @@ class PrintJobInfo { label: map['label'], lastPage: map['lastPage'], numberOfPages: map['numberOfPages'], - pageOrder: PrintJobPageOrder.fromNativeValue(map['pageOrder']), - preferredRenderingQuality: PrintJobRenderingQuality.fromNativeValue( - map['preferredRenderingQuality']), - printer: Printer.fromMap(map['printer']?.cast()), + pageOrder: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobPageOrder.fromNativeValue(map['pageOrder']), + EnumMethod.value => PrintJobPageOrder.fromValue(map['pageOrder']), + EnumMethod.name => PrintJobPageOrder.byName(map['pageOrder']) + }, + preferredRenderingQuality: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobRenderingQuality.fromNativeValue( + map['preferredRenderingQuality']), + EnumMethod.value => + PrintJobRenderingQuality.fromValue(map['preferredRenderingQuality']), + EnumMethod.name => + PrintJobRenderingQuality.byName(map['preferredRenderingQuality']) + }, + printer: Printer.fromMap(map['printer']?.cast(), + enumMethod: enumMethod), showsPrintPanel: map['showsPrintPanel'], showsProgressPanel: map['showsProgressPanel'], - state: PrintJobState.fromNativeValue(map['state']), + state: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobState.fromNativeValue(map['state']), + EnumMethod.value => PrintJobState.fromValue(map['state']), + EnumMethod.name => PrintJobState.byName(map['state']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "attributes": attributes?.toMap(), + "attributes": attributes?.toMap(enumMethod: enumMethod), "canSpawnSeparateThread": canSpawnSeparateThread, "copies": copies, "creationTime": creationTime, @@ -179,12 +197,25 @@ class PrintJobInfo { "label": label, "lastPage": lastPage, "numberOfPages": numberOfPages, - "pageOrder": pageOrder?.toNativeValue(), - "preferredRenderingQuality": preferredRenderingQuality?.toNativeValue(), - "printer": printer?.toMap(), + "pageOrder": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => pageOrder?.toNativeValue(), + EnumMethod.value => pageOrder?.toValue(), + EnumMethod.name => pageOrder?.name() + }, + "preferredRenderingQuality": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => preferredRenderingQuality?.toNativeValue(), + EnumMethod.value => preferredRenderingQuality?.toValue(), + EnumMethod.name => preferredRenderingQuality?.name() + }, + "printer": printer?.toMap(enumMethod: enumMethod), "showsPrintPanel": showsPrintPanel, "showsProgressPanel": showsProgressPanel, - "state": state?.toNativeValue(), + "state": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => state?.toNativeValue(), + EnumMethod.value => state?.toValue(), + EnumMethod.name => state?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart index 828f86609..da7fc0c0b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'print_job_media_size.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart index 038449c13..aabb967c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart @@ -417,7 +417,8 @@ class PrintJobMediaSize { required this.widthMils}); ///Gets a possible [PrintJobMediaSize] instance from a [Map] value. - static PrintJobMediaSize? fromMap(Map? map) { + static PrintJobMediaSize? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -431,7 +432,7 @@ class PrintJobMediaSize { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "heightMils": heightMils, "id": id, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart index 259125ffc..2080141fe 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart @@ -13,12 +13,18 @@ class PrintJobOrientation_ { const PrintJobOrientation_._internal(this._value); ///Pages are printed in portrait orientation. - @EnumSupportedPlatforms( - platforms: [EnumIOSPlatform(value: 0), EnumMacOSPlatform(value: 0)]) + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 0), + EnumIOSPlatform(value: 0), + EnumMacOSPlatform(value: 0), + ]) static const PORTRAIT = const PrintJobOrientation_._internal(0); ///Pages are printed in landscape orientation. - @EnumSupportedPlatforms( - platforms: [EnumIOSPlatform(value: 1), EnumMacOSPlatform(value: 1)]) + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 1), + EnumIOSPlatform(value: 1), + EnumMacOSPlatform(value: 1), + ]) static const LANDSCAPE = const PrintJobOrientation_._internal(1); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart index 334282859..15cda90d4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart @@ -19,10 +19,13 @@ class PrintJobOrientation { ///Pages are printed in landscape orientation. /// ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ///- iOS ///- MacOS static final LANDSCAPE = PrintJobOrientation._internalMultiPlatform(1, () { switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 1; case TargetPlatform.iOS: return 1; case TargetPlatform.macOS: @@ -36,10 +39,13 @@ class PrintJobOrientation { ///Pages are printed in portrait orientation. /// ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ///- iOS ///- MacOS static final PORTRAIT = PrintJobOrientation._internalMultiPlatform(0, () { switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 0; case TargetPlatform.iOS: return 0; case TargetPlatform.macOS: @@ -82,12 +88,53 @@ class PrintJobOrientation { return null; } + /// Gets a possible [PrintJobOrientation] instance value with name [name]. + /// + /// Goes through [PrintJobOrientation.values] looking for a value with + /// name [name], as reported by [PrintJobOrientation.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobOrientation? byName(String? name) { + if (name != null) { + try { + return PrintJobOrientation.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobOrientation] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobOrientation.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'LANDSCAPE'; + case 0: + return 'PORTRAIT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -96,12 +143,6 @@ class PrintJobOrientation { @override String toString() { - switch (_value) { - case 1: - return 'LANDSCAPE'; - case 0: - return 'PORTRAIT'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart index c4b7ca7ff..a4d7a9945 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart @@ -72,20 +72,44 @@ class PrintJobOutputType { return null; } + /// Gets a possible [PrintJobOutputType] instance value with name [name]. + /// + /// Goes through [PrintJobOutputType.values] looking for a value with + /// name [name], as reported by [PrintJobOutputType.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobOutputType? byName(String? name) { + if (name != null) { + try { + return PrintJobOutputType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobOutputType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobOutputType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'GENERAL'; @@ -98,4 +122,15 @@ class PrintJobOutputType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart index bcbee904a..2274d4155 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart @@ -106,20 +106,44 @@ class PrintJobPageOrder { return null; } + /// Gets a possible [PrintJobPageOrder] instance value with name [name]. + /// + /// Goes through [PrintJobPageOrder.values] looking for a value with + /// name [name], as reported by [PrintJobPageOrder.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobPageOrder? byName(String? name) { + if (name != null) { + try { + return PrintJobPageOrder.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobPageOrder] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobPageOrder.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ASCENDING'; @@ -132,4 +156,15 @@ class PrintJobPageOrder { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart index ec1da7bb5..ac7bf9986 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart @@ -92,12 +92,55 @@ class PrintJobPaginationMode { return null; } + /// Gets a possible [PrintJobPaginationMode] instance value with name [name]. + /// + /// Goes through [PrintJobPaginationMode.values] looking for a value with + /// name [name], as reported by [PrintJobPaginationMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobPaginationMode? byName(String? name) { + if (name != null) { + try { + return PrintJobPaginationMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobPaginationMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobPaginationMode.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'AUTOMATIC': + return 'AUTOMATIC'; + case 'CLIP': + return 'CLIP'; + case 'FIT': + return 'FIT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart index 70af46e3a..0a4783cd8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart @@ -84,12 +84,53 @@ class PrintJobRenderingQuality { return null; } + /// Gets a possible [PrintJobRenderingQuality] instance value with name [name]. + /// + /// Goes through [PrintJobRenderingQuality.values] looking for a value with + /// name [name], as reported by [PrintJobRenderingQuality.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobRenderingQuality? byName(String? name) { + if (name != null) { + try { + return PrintJobRenderingQuality.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobRenderingQuality] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobRenderingQuality.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'BEST'; + case 1: + return 'RESPONSIVE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -98,12 +139,6 @@ class PrintJobRenderingQuality { @override String toString() { - switch (_value) { - case 0: - return 'BEST'; - case 1: - return 'RESPONSIVE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart index cbbfd3c0f..f2ea665c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'print_job_resolution.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart index d5827269e..21a003182 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart @@ -34,7 +34,8 @@ class PrintJobResolution { required this.verticalDpi}); ///Gets a possible [PrintJobResolution] instance from a [Map] value. - static PrintJobResolution? fromMap(Map? map) { + static PrintJobResolution? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -48,7 +49,7 @@ class PrintJobResolution { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "horizontalDpi": horizontalDpi, "id": id, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart index 8a1efff62..082b1cea8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart @@ -192,20 +192,43 @@ class PrintJobState { return null; } + /// Gets a possible [PrintJobState] instance value with name [name]. + /// + /// Goes through [PrintJobState.values] looking for a value with + /// name [name], as reported by [PrintJobState.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobState? byName(String? name) { + if (name != null) { + try { + return PrintJobState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in PrintJobState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'BLOCKED'; @@ -224,4 +247,15 @@ class PrintJobState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/printer.dart b/flutter_inappwebview_platform_interface/lib/src/types/printer.dart index f66c67b92..2216b49c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/printer.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/printer.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'printer.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart index ec54b75cf..6c95e6a25 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart @@ -35,7 +35,7 @@ class Printer { Printer({this.id, this.languageLevel, this.name, this.type}); ///Gets a possible [Printer] instance from a [Map] value. - static Printer? fromMap(Map? map) { + static Printer? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -49,7 +49,7 @@ class Printer { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, "languageLevel": languageLevel, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart new file mode 100644 index 000000000..972807f91 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart @@ -0,0 +1,77 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'frame_info.dart'; +import 'process_failed_kind.dart'; +import 'process_failed_reason.dart'; +import 'enum_method.dart'; + +part 'process_failed_detail.g.dart'; + +///An object that contains information about a frame on a webpage. +@ExchangeableObject() +class ProcessFailedDetail_ { + ///The kind of process failure that has occurred. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + ProcessFailedKind_ kind; + + ///The exit code of the failing process, for telemetry purposes. + /// + ///The exit code is always STILL_ACTIVE (259) when [ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? exitCode; + + ///Description of the process assigned by the WebView2 Runtime. + /// + ///This is a technical English term appropriate for logging or development purposes, and not localized for the end user. + ///It applies to utility processes (for example, "Audio Service", "Video Capture") and plugin processes (for example, "Flash"). + ///The returned [processDescription] is empty if the WebView2 Runtime did not assign a description to the process. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? processDescription; + + ///The reason for the process failure. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + ProcessFailedReason_? reason; + + ///This property is the full path of the module that caused the crash in cases of Windows Code Integrity failures. + /// + ///Windows Code Integrity is a feature that verifies the integrity and authenticity of dynamic-link libraries (DLLs) on Windows systems. + ///It ensures that only trusted code can run on the system and prevents unauthorized or malicious modifications. + ///When ProcessFailed occurred due to a failed Code Integrity check, this property returns the full path of the file that was prevented from loading on the system. + ///The webview2 process which tried to load the DLL will fail with exit code STATUS_INVALID_IMAGE_HASH(-1073740760). + ///A file can fail integrity check for various reasons, such as: + ///- It has an invalid or missing signature that does not match the publisher or signer of the file. + ///- It has been tampered with or corrupted by malware or other software. + ///- It has been blocked by an administrator or a security policy. This property always will be the empty string if failure is not caused by STATUS_INVALID_IMAGE_HASH. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? failureSourceModulePath; + + ///The collection of [FrameInfo]s for frames in the WebView that were being rendered by the failed process. + /// + ///The content in these frames is replaced with an error page. + ///This is only available when [ProcessFailedKind] is [ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED]; + ///frames is null for all other process failure kinds, including the case in which the failed process was the renderer + ///for the main frame and subframes within it, for which the failure kind is [ProcessFailedKind.RENDER_PROCESS_EXITED]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + List? frameInfos; + + ProcessFailedDetail_({ + required this.kind, + this.exitCode, + this.processDescription, + this.reason, + this.failureSourceModulePath, + this.frameInfos, + }); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart new file mode 100644 index 000000000..6b0aa7f32 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart @@ -0,0 +1,135 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_detail.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///An object that contains information about a frame on a webpage. +class ProcessFailedDetail { + ///The exit code of the failing process, for telemetry purposes. + /// + ///The exit code is always STILL_ACTIVE (259) when [ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? exitCode; + + ///This property is the full path of the module that caused the crash in cases of Windows Code Integrity failures. + /// + ///Windows Code Integrity is a feature that verifies the integrity and authenticity of dynamic-link libraries (DLLs) on Windows systems. + ///It ensures that only trusted code can run on the system and prevents unauthorized or malicious modifications. + ///When ProcessFailed occurred due to a failed Code Integrity check, this property returns the full path of the file that was prevented from loading on the system. + ///The webview2 process which tried to load the DLL will fail with exit code STATUS_INVALID_IMAGE_HASH(-1073740760). + ///A file can fail integrity check for various reasons, such as: + ///- It has an invalid or missing signature that does not match the publisher or signer of the file. + ///- It has been tampered with or corrupted by malware or other software. + ///- It has been blocked by an administrator or a security policy. This property always will be the empty string if failure is not caused by STATUS_INVALID_IMAGE_HASH. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? failureSourceModulePath; + + ///The collection of [FrameInfo]s for frames in the WebView that were being rendered by the failed process. + /// + ///The content in these frames is replaced with an error page. + ///This is only available when [ProcessFailedKind] is [ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED]; + ///frames is null for all other process failure kinds, including the case in which the failed process was the renderer + ///for the main frame and subframes within it, for which the failure kind is [ProcessFailedKind.RENDER_PROCESS_EXITED]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + List? frameInfos; + + ///The kind of process failure that has occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ProcessFailedKind kind; + + ///Description of the process assigned by the WebView2 Runtime. + /// + ///This is a technical English term appropriate for logging or development purposes, and not localized for the end user. + ///It applies to utility processes (for example, "Audio Service", "Video Capture") and plugin processes (for example, "Flash"). + ///The returned [processDescription] is empty if the WebView2 Runtime did not assign a description to the process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? processDescription; + + ///The reason for the process failure. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ProcessFailedReason? reason; + ProcessFailedDetail( + {this.exitCode, + this.failureSourceModulePath, + this.frameInfos, + required this.kind, + this.processDescription, + this.reason}); + + ///Gets a possible [ProcessFailedDetail] instance from a [Map] value. + static ProcessFailedDetail? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ProcessFailedDetail( + exitCode: map['exitCode'], + failureSourceModulePath: map['failureSourceModulePath'], + frameInfos: map['frameInfos'] != null + ? List.from(map['frameInfos'].map((e) => FrameInfo.fromMap( + e?.cast(), + enumMethod: enumMethod)!)) + : null, + kind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProcessFailedKind.fromNativeValue(map['kind']), + EnumMethod.value => ProcessFailedKind.fromValue(map['kind']), + EnumMethod.name => ProcessFailedKind.byName(map['kind']) + }!, + processDescription: map['processDescription'], + reason: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProcessFailedReason.fromNativeValue(map['reason']), + EnumMethod.value => ProcessFailedReason.fromValue(map['reason']), + EnumMethod.name => ProcessFailedReason.byName(map['reason']) + }, + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "exitCode": exitCode, + "failureSourceModulePath": failureSourceModulePath, + "frameInfos": + frameInfos?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "kind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => kind.toNativeValue(), + EnumMethod.value => kind.toValue(), + EnumMethod.name => kind.name() + }, + "processDescription": processDescription, + "reason": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => reason?.toNativeValue(), + EnumMethod.value => reason?.toValue(), + EnumMethod.name => reason?.name() + }, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ProcessFailedDetail{exitCode: $exitCode, failureSourceModulePath: $failureSourceModulePath, frameInfos: $frameInfos, kind: $kind, processDescription: $processDescription, reason: $reason}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart new file mode 100644 index 000000000..cee266e32 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart @@ -0,0 +1,152 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'process_failed_kind.g.dart'; + +///Class used to indicate the kind of process failure that has occurred. +@ExchangeableEnum() +class ProcessFailedKind_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const ProcessFailedKind_._internal(this._value); + + ///Indicates that the browser process ended unexpectedly. The WebView automatically moves to the Closed state. + ///The app has to recreate a new WebView to recover from this failure. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 0), + ]) + static const BROWSER_PROCESS_EXITED = + const ProcessFailedKind_._internal('BROWSER_PROCESS_EXITED'); + + ///Indicates that the main frame's render process ended unexpectedly. Any subframes in the WebView will be gone too. + ///A new render process is created automatically and navigated to an error page. + ///You can use the reload method to try to recover from this failure. Alternatively, you can close and recreate the WebView. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 1), + ]) + static const RENDER_PROCESS_EXITED = + const ProcessFailedKind_._internal('RENDER_PROCESS_EXITED'); + + ///Indicates that the main frame's render process is unresponsive. + ///Renderer process unresponsiveness can happen for the following reasons: + /// + ///* There is a **long-running script** being executed. For example, the + ///web content in your WebView might be performing a synchronous XHR, or have + ///entered an infinite loop. + ///* The **system is busy**. + /// + ///The process failed event will continue to be raised every few seconds + ///until the renderer process has become responsive again. The application + ///can consider taking action if the event keeps being raised. For example, + ///the application might show UI for the user to decide to keep waiting or + ///reload the page, or navigate away. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 2), + ]) + static const RENDER_PROCESS_UNRESPONSIVE = + const ProcessFailedKind_._internal('RENDER_PROCESS_UNRESPONSIVE'); + + ///Indicates that a frame-only render process ended unexpectedly. + ///The process exit does not affect the top-level document, only a subset of the subframes within it. + ///The content in these frames is replaced with an error page in the frame. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 3), + ]) + static const FRAME_RENDER_PROCESS_EXITED = + const ProcessFailedKind_._internal('FRAME_RENDER_PROCESS_EXITED'); + + ///Indicates that a utility process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 4), + ]) + static const UTILITY_PROCESS_EXITED = + const ProcessFailedKind_._internal('UTILITY_PROCESS_EXITED'); + + ///Indicates that a sandbox helper process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 5), + ]) + static const SANDBOX_HELPER_PROCESS_EXITED = + const ProcessFailedKind_._internal('SANDBOX_HELPER_PROCESS_EXITED'); + + ///Indicates that the GPU process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 6), + ]) + static const GPU_PROCESS_EXITED = + const ProcessFailedKind_._internal('GPU_PROCESS_EXITED'); + + ///Indicates that a PPAPI plugin process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 7), + ]) + static const PPAPI_PLUGIN_PROCESS_EXITED = + const ProcessFailedKind_._internal('PPAPI_PLUGIN_PROCESS_EXITED'); + + ///Indicates that a PPAPI plugin broker process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 8), + ]) + static const PPAPI_BROKER_PROCESS_EXITED = + const ProcessFailedKind_._internal('PPAPI_BROKER_PROCESS_EXITED'); + + ///Indicates that a process of unspecified kind ended unexpectedly. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 9), + ]) + static const UNKNOWN_PROCESS_EXITED = + const ProcessFailedKind_._internal('UNKNOWN_PROCESS_EXITED'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart new file mode 100644 index 000000000..729477570 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart @@ -0,0 +1,314 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_kind.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the kind of process failure that has occurred. +class ProcessFailedKind { + final String _value; + final dynamic _nativeValue; + const ProcessFailedKind._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ProcessFailedKind._internalMultiPlatform( + String value, Function nativeValue) => + ProcessFailedKind._internal(value, nativeValue()); + + ///Indicates that the browser process ended unexpectedly. The WebView automatically moves to the Closed state. + ///The app has to recreate a new WebView to recover from this failure. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final BROWSER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('BROWSER_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///Indicates that a frame-only render process ended unexpectedly. + ///The process exit does not affect the top-level document, only a subset of the subframes within it. + ///The content in these frames is replaced with an error page in the frame. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final FRAME_RENDER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('FRAME_RENDER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///Indicates that the GPU process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final GPU_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('GPU_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 6; + default: + break; + } + return null; + }); + + ///Indicates that a PPAPI plugin broker process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final PPAPI_BROKER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('PPAPI_BROKER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 8; + default: + break; + } + return null; + }); + + ///Indicates that a PPAPI plugin process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final PPAPI_PLUGIN_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('PPAPI_PLUGIN_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 7; + default: + break; + } + return null; + }); + + ///Indicates that the main frame's render process ended unexpectedly. Any subframes in the WebView will be gone too. + ///A new render process is created automatically and navigated to an error page. + ///You can use the reload method to try to recover from this failure. Alternatively, you can close and recreate the WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final RENDER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('RENDER_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Indicates that the main frame's render process is unresponsive. + ///Renderer process unresponsiveness can happen for the following reasons: + /// + ///* There is a **long-running script** being executed. For example, the + ///web content in your WebView might be performing a synchronous XHR, or have + ///entered an infinite loop. + ///* The **system is busy**. + /// + ///The process failed event will continue to be raised every few seconds + ///until the renderer process has become responsive again. The application + ///can consider taking action if the event keeps being raised. For example, + ///the application might show UI for the user to decide to keep waiting or + ///reload the page, or navigate away. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final RENDER_PROCESS_UNRESPONSIVE = + ProcessFailedKind._internalMultiPlatform('RENDER_PROCESS_UNRESPONSIVE', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///Indicates that a sandbox helper process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final SANDBOX_HELPER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('SANDBOX_HELPER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 5; + default: + break; + } + return null; + }); + + ///Indicates that a process of unspecified kind ended unexpectedly. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final UNKNOWN_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('UNKNOWN_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 9; + default: + break; + } + return null; + }); + + ///Indicates that a utility process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final UTILITY_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('UTILITY_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///Set of all values of [ProcessFailedKind]. + static final Set values = [ + ProcessFailedKind.BROWSER_PROCESS_EXITED, + ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED, + ProcessFailedKind.GPU_PROCESS_EXITED, + ProcessFailedKind.PPAPI_BROKER_PROCESS_EXITED, + ProcessFailedKind.PPAPI_PLUGIN_PROCESS_EXITED, + ProcessFailedKind.RENDER_PROCESS_EXITED, + ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE, + ProcessFailedKind.SANDBOX_HELPER_PROCESS_EXITED, + ProcessFailedKind.UNKNOWN_PROCESS_EXITED, + ProcessFailedKind.UTILITY_PROCESS_EXITED, + ].toSet(); + + ///Gets a possible [ProcessFailedKind] instance from [String] value. + static ProcessFailedKind? fromValue(String? value) { + if (value != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ProcessFailedKind] instance from a native value. + static ProcessFailedKind? fromNativeValue(dynamic value) { + if (value != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ProcessFailedKind] instance value with name [name]. + /// + /// Goes through [ProcessFailedKind.values] looking for a value with + /// name [name], as reported by [ProcessFailedKind.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProcessFailedKind? byName(String? name) { + if (name != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProcessFailedKind] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProcessFailedKind.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'BROWSER_PROCESS_EXITED': + return 'BROWSER_PROCESS_EXITED'; + case 'FRAME_RENDER_PROCESS_EXITED': + return 'FRAME_RENDER_PROCESS_EXITED'; + case 'GPU_PROCESS_EXITED': + return 'GPU_PROCESS_EXITED'; + case 'PPAPI_BROKER_PROCESS_EXITED': + return 'PPAPI_BROKER_PROCESS_EXITED'; + case 'PPAPI_PLUGIN_PROCESS_EXITED': + return 'PPAPI_PLUGIN_PROCESS_EXITED'; + case 'RENDER_PROCESS_EXITED': + return 'RENDER_PROCESS_EXITED'; + case 'RENDER_PROCESS_UNRESPONSIVE': + return 'RENDER_PROCESS_UNRESPONSIVE'; + case 'SANDBOX_HELPER_PROCESS_EXITED': + return 'SANDBOX_HELPER_PROCESS_EXITED'; + case 'UNKNOWN_PROCESS_EXITED': + return 'UNKNOWN_PROCESS_EXITED'; + case 'UTILITY_PROCESS_EXITED': + return 'UTILITY_PROCESS_EXITED'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart new file mode 100644 index 000000000..647e6e887 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart @@ -0,0 +1,77 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'process_failed_reason.g.dart'; + +///Class used to indicate the kind of process failure that has occurred. +@ExchangeableEnum() +class ProcessFailedReason_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const ProcessFailedReason_._internal(this._value); + + ///An unexpected process failure occurred. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 0), + ]) + static const UNEXPECTED = const ProcessFailedReason_._internal('UNEXPECTED'); + + ///The process became unresponsive. This only applies to the main frame's render process. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_UNRESPONSIVE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 1), + ]) + static const UNRESPONSIVE = + const ProcessFailedReason_._internal('UNRESPONSIVE'); + + ///The process was terminated. For example, from Task Manager. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_TERMINATED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 2), + ]) + static const TERMINATED = const ProcessFailedReason_._internal('TERMINATED'); + + ///The process crashed. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 3), + ]) + static const CRASHED = const ProcessFailedReason_._internal('CRASHED'); + + ///The process failed to launch. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_LAUNCH_FAILED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 4), + ]) + static const LAUNCH_FAILED = + const ProcessFailedReason_._internal('LAUNCH_FAILED'); + + ///The process terminated due to running out of memory. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_OUT_OF_MEMORY', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 5), + ]) + static const OUT_OF_MEMORY = + const ProcessFailedReason_._internal('OUT_OF_MEMORY'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart new file mode 100644 index 000000000..648cc1447 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart @@ -0,0 +1,210 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_reason.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the kind of process failure that has occurred. +class ProcessFailedReason { + final String _value; + final dynamic _nativeValue; + const ProcessFailedReason._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ProcessFailedReason._internalMultiPlatform( + String value, Function nativeValue) => + ProcessFailedReason._internal(value, nativeValue()); + + ///The process crashed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final CRASHED = + ProcessFailedReason._internalMultiPlatform('CRASHED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///The process failed to launch. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_LAUNCH_FAILED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final LAUNCH_FAILED = + ProcessFailedReason._internalMultiPlatform('LAUNCH_FAILED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///The process terminated due to running out of memory. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_OUT_OF_MEMORY](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final OUT_OF_MEMORY = + ProcessFailedReason._internalMultiPlatform('OUT_OF_MEMORY', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 5; + default: + break; + } + return null; + }); + + ///The process was terminated. For example, from Task Manager. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_TERMINATED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final TERMINATED = + ProcessFailedReason._internalMultiPlatform('TERMINATED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///An unexpected process failure occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final UNEXPECTED = + ProcessFailedReason._internalMultiPlatform('UNEXPECTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///The process became unresponsive. This only applies to the main frame's render process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_UNRESPONSIVE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final UNRESPONSIVE = + ProcessFailedReason._internalMultiPlatform('UNRESPONSIVE', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Set of all values of [ProcessFailedReason]. + static final Set values = [ + ProcessFailedReason.CRASHED, + ProcessFailedReason.LAUNCH_FAILED, + ProcessFailedReason.OUT_OF_MEMORY, + ProcessFailedReason.TERMINATED, + ProcessFailedReason.UNEXPECTED, + ProcessFailedReason.UNRESPONSIVE, + ].toSet(); + + ///Gets a possible [ProcessFailedReason] instance from [String] value. + static ProcessFailedReason? fromValue(String? value) { + if (value != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ProcessFailedReason] instance from a native value. + static ProcessFailedReason? fromNativeValue(dynamic value) { + if (value != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ProcessFailedReason] instance value with name [name]. + /// + /// Goes through [ProcessFailedReason.values] looking for a value with + /// name [name], as reported by [ProcessFailedReason.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProcessFailedReason? byName(String? name) { + if (name != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProcessFailedReason] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProcessFailedReason.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CRASHED': + return 'CRASHED'; + case 'LAUNCH_FAILED': + return 'LAUNCH_FAILED'; + case 'OUT_OF_MEMORY': + return 'OUT_OF_MEMORY'; + case 'TERMINATED': + return 'TERMINATED'; + case 'UNEXPECTED': + return 'UNEXPECTED'; + case 'UNRESPONSIVE': + return 'UNRESPONSIVE'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart new file mode 100644 index 000000000..52058f542 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart @@ -0,0 +1,46 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'enum_method.dart'; + +part 'proxy_relay_hop.g.dart'; + +///Relay servers are secure HTTP proxies that allow proxying TCP traffic using the +///CONNECT method and UDP traffic using the connect-udp protocol defined in [RFC 9298](https://www.rfc-editor.org/rfc/rfc9298.html). +/// +///If [http3RelayEndpoint] is not null, it creates a configuration for a secure relay accessible using HTTP/3, with an optional HTTP/2 fallback using [http2RelayEndpoint]. +///Otherwise, if [http2RelayEndpoint] is not null, it sreates a configuration for a secure relay accessible only using HTTP/2. +/// +///At least one of [http3RelayEndpoint] or [http2RelayEndpoint] must be non-null. +@ExchangeableObject() +class ProxyRelayHop_ { + ///A URL or host endpoint identifying the relay server accessible using HTTP/3. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? http3RelayEndpoint; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/2. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? http2RelayEndpoint; + + ///A dictionary of additional HTTP headers to send as part of CONNECT requests to the relay. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + Map? additionalHTTPHeaders; + + @ExchangeableObjectConstructor() + ProxyRelayHop_({ + this.http3RelayEndpoint, + this.http2RelayEndpoint, + this.additionalHTTPHeaders, + }) { + assert(http3RelayEndpoint != null || http2RelayEndpoint != null, + "At least one of http3RelayEndpoint or http2RelayEndpoint must be non-null"); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart new file mode 100644 index 000000000..f7ffce3b2 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart @@ -0,0 +1,78 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'proxy_relay_hop.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Relay servers are secure HTTP proxies that allow proxying TCP traffic using the +///CONNECT method and UDP traffic using the connect-udp protocol defined in [RFC 9298](https://www.rfc-editor.org/rfc/rfc9298.html). +/// +///If [http3RelayEndpoint] is not null, it creates a configuration for a secure relay accessible using HTTP/3, with an optional HTTP/2 fallback using [http2RelayEndpoint]. +///Otherwise, if [http2RelayEndpoint] is not null, it sreates a configuration for a secure relay accessible only using HTTP/2. +/// +///At least one of [http3RelayEndpoint] or [http2RelayEndpoint] must be non-null. +class ProxyRelayHop { + ///A dictionary of additional HTTP headers to send as part of CONNECT requests to the relay. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + Map? additionalHTTPHeaders; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/2. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? http2RelayEndpoint; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/3. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? http3RelayEndpoint; + ProxyRelayHop( + {this.http3RelayEndpoint, + this.http2RelayEndpoint, + this.additionalHTTPHeaders}) { + assert(http3RelayEndpoint != null || http2RelayEndpoint != null, + "At least one of http3RelayEndpoint or http2RelayEndpoint must be non-null"); + } + + ///Gets a possible [ProxyRelayHop] instance from a [Map] value. + static ProxyRelayHop? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ProxyRelayHop( + additionalHTTPHeaders: + map['additionalHTTPHeaders']?.cast(), + http2RelayEndpoint: map['http2RelayEndpoint'], + http3RelayEndpoint: map['http3RelayEndpoint'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "additionalHTTPHeaders": additionalHTTPHeaders, + "http2RelayEndpoint": http2RelayEndpoint, + "http3RelayEndpoint": http3RelayEndpoint, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ProxyRelayHop{additionalHTTPHeaders: $additionalHTTPHeaders, http2RelayEndpoint: $http2RelayEndpoint, http3RelayEndpoint: $http3RelayEndpoint}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart index 031be516d..2a343f0c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'proxy_scheme_filter.dart'; part 'proxy_rule.g.dart'; @@ -8,10 +9,64 @@ part 'proxy_rule.g.dart'; @ExchangeableObject() class ProxyRule_ { ///Represents the scheme filter. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + ]) ProxySchemeFilter_? schemeFilter; ///Represents the proxy URL. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + ]) String url; - ProxyRule_({required this.url, this.schemeFilter}); + ///A Boolean that indicates whether or not a proxy configuration allows failover to non-proxied connections. + ///Failover isn’t allowed by default. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + bool? allowFailover; + + ///Sets a username to use as authentication for a proxy configuration. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? username; + + ///Sets a password to use as authentication for a proxy configuration. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? password; + + ///Define an array of domains to determine which hosts should not use the proxy. + ///If the array is empty, no domains are excluded. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + List? excludedDomains; + + ///Define an array of domains to determine which hosts should use the proxy. If the array is empty, + ///all domains are allowed to use the proxy other than domains listed in [excludedDomains]. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + List? matchDomains; + + ProxyRule_({ + required this.url, + this.schemeFilter, + this.allowFailover, + this.username, + this.password, + this.excludedDomains, + this.matchDomains, + }); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart index f81094c5c..02115af81 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart @@ -8,30 +8,107 @@ part of 'proxy_rule.dart'; ///Class that holds a scheme filter and a proxy URL. class ProxyRule { + ///A Boolean that indicates whether or not a proxy configuration allows failover to non-proxied connections. + ///Failover isn’t allowed by default. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + bool? allowFailover; + + ///Define an array of domains to determine which hosts should not use the proxy. + ///If the array is empty, no domains are excluded. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + List? excludedDomains; + + ///Define an array of domains to determine which hosts should use the proxy. If the array is empty, + ///all domains are allowed to use the proxy other than domains listed in [excludedDomains]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + List? matchDomains; + + ///Sets a password to use as authentication for a proxy configuration. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? password; + ///Represents the scheme filter. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ProxySchemeFilter? schemeFilter; ///Represents the proxy URL. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS String url; - ProxyRule({this.schemeFilter, required this.url}); + + ///Sets a username to use as authentication for a proxy configuration. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? username; + ProxyRule( + {this.allowFailover, + this.excludedDomains, + this.matchDomains, + this.password, + this.schemeFilter, + required this.url, + this.username}); ///Gets a possible [ProxyRule] instance from a [Map] value. - static ProxyRule? fromMap(Map? map) { + static ProxyRule? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ProxyRule( - schemeFilter: ProxySchemeFilter.fromNativeValue(map['schemeFilter']), + allowFailover: map['allowFailover'], + excludedDomains: map['excludedDomains'] != null + ? List.from(map['excludedDomains']!.cast()) + : null, + matchDomains: map['matchDomains'] != null + ? List.from(map['matchDomains']!.cast()) + : null, + password: map['password'], + schemeFilter: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProxySchemeFilter.fromNativeValue(map['schemeFilter']), + EnumMethod.value => ProxySchemeFilter.fromValue(map['schemeFilter']), + EnumMethod.name => ProxySchemeFilter.byName(map['schemeFilter']) + }, url: map['url'], + username: map['username'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "schemeFilter": schemeFilter?.toNativeValue(), + "allowFailover": allowFailover, + "excludedDomains": excludedDomains, + "matchDomains": matchDomains, + "password": password, + "schemeFilter": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => schemeFilter?.toNativeValue(), + EnumMethod.value => schemeFilter?.toValue(), + EnumMethod.name => schemeFilter?.name() + }, "url": url, + "username": username, }; } @@ -42,6 +119,6 @@ class ProxyRule { @override String toString() { - return 'ProxyRule{schemeFilter: $schemeFilter, url: $url}'; + return 'ProxyRule{allowFailover: $allowFailover, excludedDomains: $excludedDomains, matchDomains: $matchDomains, password: $password, schemeFilter: $schemeFilter, url: $url, username: $username}'; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart index 7abc64a01..56b9414e3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart @@ -58,12 +58,55 @@ class ProxySchemeFilter { return null; } + /// Gets a possible [ProxySchemeFilter] instance value with name [name]. + /// + /// Goes through [ProxySchemeFilter.values] looking for a value with + /// name [name], as reported by [ProxySchemeFilter.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProxySchemeFilter? byName(String? name) { + if (name != null) { + try { + return ProxySchemeFilter.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProxySchemeFilter] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProxySchemeFilter.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case '*': + return 'MATCH_ALL_SCHEMES'; + case 'http': + return 'MATCH_HTTP'; + case 'https': + return 'MATCH_HTTPS'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart index bb53e8757..4a518d8df 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart @@ -54,12 +54,53 @@ class PullToRefreshSize { return null; } + /// Gets a possible [PullToRefreshSize] instance value with name [name]. + /// + /// Goes through [PullToRefreshSize.values] looking for a value with + /// name [name], as reported by [PullToRefreshSize.name]. + /// Returns the first value with the given name, otherwise `null`. + static PullToRefreshSize? byName(String? name) { + if (name != null) { + try { + return PullToRefreshSize.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PullToRefreshSize] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PullToRefreshSize.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'DEFAULT'; + case 0: + return 'LARGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,13 +109,7 @@ class PullToRefreshSize { @override String toString() { - switch (_value) { - case 1: - return 'DEFAULT'; - case 0: - return 'LARGE'; - } - return _value.toString(); + return name(); } } @@ -128,12 +163,53 @@ class AndroidPullToRefreshSize { return null; } + /// Gets a possible [AndroidPullToRefreshSize] instance value with name [name]. + /// + /// Goes through [AndroidPullToRefreshSize.values] looking for a value with + /// name [name], as reported by [AndroidPullToRefreshSize.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidPullToRefreshSize? byName(String? name) { + if (name != null) { + try { + return AndroidPullToRefreshSize.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidPullToRefreshSize] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidPullToRefreshSize.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'DEFAULT'; + case 0: + return 'LARGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -142,12 +218,6 @@ class AndroidPullToRefreshSize { @override String toString() { - switch (_value) { - case 1: - return 'DEFAULT'; - case 0: - return 'LARGE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart index db31b4b3d..6ea14e309 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart @@ -91,12 +91,64 @@ class ReferrerPolicy { return null; } + /// Gets a possible [ReferrerPolicy] instance value with name [name]. + /// + /// Goes through [ReferrerPolicy.values] looking for a value with + /// name [name], as reported by [ReferrerPolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static ReferrerPolicy? byName(String? name) { + if (name != null) { + try { + return ReferrerPolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ReferrerPolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in ReferrerPolicy.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'no-referrer': + return 'NO_REFERRER'; + case 'no-referrer-when-downgrade': + return 'NO_REFERRER_WHEN_DOWNGRADE'; + case 'origin': + return 'ORIGIN'; + case 'origin-when-cross-origin': + return 'ORIGIN_WHEN_CROSS_ORIGIN'; + case 'same-origin': + return 'SAME_ORIGIN'; + case 'strict-origin': + return 'STRICT_ORIGIN'; + case 'strict-origin-when-cross-origin': + return 'STRICT_ORIGIN_WHEN_CROSS_ORIGIN'; + case 'unsafe-url': + return 'UNSAFE_URL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart index 043a3cc82..c78c675d8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'renderer_priority.dart'; +import 'enum_method.dart'; part 'render_process_gone_detail.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart index 39f2c3421..ae18a1e7f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart @@ -21,23 +21,34 @@ class RenderProcessGoneDetail { {required this.didCrash, this.rendererPriorityAtExit}); ///Gets a possible [RenderProcessGoneDetail] instance from a [Map] value. - static RenderProcessGoneDetail? fromMap(Map? map) { + static RenderProcessGoneDetail? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = RenderProcessGoneDetail( didCrash: map['didCrash'], - rendererPriorityAtExit: + rendererPriorityAtExit: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => RendererPriority.fromNativeValue(map['rendererPriorityAtExit']), + EnumMethod.value => + RendererPriority.fromValue(map['rendererPriorityAtExit']), + EnumMethod.name => + RendererPriority.byName(map['rendererPriorityAtExit']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "didCrash": didCrash, - "rendererPriorityAtExit": rendererPriorityAtExit?.toNativeValue(), + "rendererPriorityAtExit": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => rendererPriorityAtExit?.toNativeValue(), + EnumMethod.value => rendererPriorityAtExit?.toValue(), + EnumMethod.name => rendererPriorityAtExit?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart index 2b2b7567a..e8116339a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart @@ -59,20 +59,44 @@ class RendererPriority { return null; } + /// Gets a possible [RendererPriority] instance value with name [name]. + /// + /// Goes through [RendererPriority.values] looking for a value with + /// name [name], as reported by [RendererPriority.name]. + /// Returns the first value with the given name, otherwise `null`. + static RendererPriority? byName(String? name) { + if (name != null) { + try { + return RendererPriority.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [RendererPriority] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in RendererPriority.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'RENDERER_PRIORITY_BOUND'; @@ -83,4 +107,15 @@ class RendererPriority { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart index 8c599e964..5bb4fc794 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'renderer_priority.dart'; +import 'enum_method.dart'; part 'renderer_priority_policy.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart index 1ef810ea8..9e42b49a1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart @@ -22,22 +22,34 @@ class RendererPriorityPolicy { {this.rendererRequestedPriority, required this.waivedWhenNotVisible}); ///Gets a possible [RendererPriorityPolicy] instance from a [Map] value. - static RendererPriorityPolicy? fromMap(Map? map) { + static RendererPriorityPolicy? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = RendererPriorityPolicy( - rendererRequestedPriority: + rendererRequestedPriority: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => RendererPriority.fromNativeValue(map['rendererRequestedPriority']), + EnumMethod.value => + RendererPriority.fromValue(map['rendererRequestedPriority']), + EnumMethod.name => + RendererPriority.byName(map['rendererRequestedPriority']) + }, waivedWhenNotVisible: map['waivedWhenNotVisible'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rendererRequestedPriority": rendererRequestedPriority?.toNativeValue(), + "rendererRequestedPriority": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => rendererRequestedPriority?.toNativeValue(), + EnumMethod.value => rendererRequestedPriority?.toValue(), + EnumMethod.name => rendererRequestedPriority?.name() + }, "waivedWhenNotVisible": waivedWhenNotVisible, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart index 77f56c001..14d9d840c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'request_focus_node_href_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart index 57c859404..eeea135fb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart @@ -19,7 +19,8 @@ class RequestFocusNodeHrefResult { RequestFocusNodeHrefResult({this.src, this.title, this.url}); ///Gets a possible [RequestFocusNodeHrefResult] instance from a [Map] value. - static RequestFocusNodeHrefResult? fromMap(Map? map) { + static RequestFocusNodeHrefResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -32,7 +33,7 @@ class RequestFocusNodeHrefResult { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "src": src, "title": title, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart index 0c5c622f3..c9f4eefc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'request_image_ref_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart index bb42cf518..758a7e002 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart @@ -13,7 +13,8 @@ class RequestImageRefResult { RequestImageRefResult({this.url}); ///Gets a possible [RequestImageRefResult] instance from a [Map] value. - static RequestImageRefResult? fromMap(Map? map) { + static RequestImageRefResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -24,7 +25,7 @@ class RequestImageRefResult { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "url": url?.toString(), }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart index 35935cbba..940b52434 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../in_app_webview/platform_webview.dart'; import 'safe_browsing_response_action.dart'; +import 'enum_method.dart'; part 'safe_browsing_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart index 4c24640e5..ffd1ae3a6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart @@ -19,20 +19,32 @@ class SafeBrowsingResponse { this.report = true}); ///Gets a possible [SafeBrowsingResponse] instance from a [Map] value. - static SafeBrowsingResponse? fromMap(Map? map) { + static SafeBrowsingResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = SafeBrowsingResponse(); - instance.action = SafeBrowsingResponseAction.fromNativeValue(map['action']); - instance.report = map['report']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + SafeBrowsingResponseAction.fromNativeValue(map['action']), + EnumMethod.value => SafeBrowsingResponseAction.fromValue(map['action']), + EnumMethod.name => SafeBrowsingResponseAction.byName(map['action']) + }; + if (map['report'] != null) { + instance.report = map['report']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "report": report, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart index c435bb404..5a1645263 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart @@ -58,20 +58,45 @@ class SafeBrowsingResponseAction { return null; } + /// Gets a possible [SafeBrowsingResponseAction] instance value with name [name]. + /// + /// Goes through [SafeBrowsingResponseAction.values] looking for a value with + /// name [name], as reported by [SafeBrowsingResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static SafeBrowsingResponseAction? byName(String? name) { + if (name != null) { + try { + return SafeBrowsingResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SafeBrowsingResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SafeBrowsingResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'BACK_TO_SAFETY'; @@ -82,4 +107,15 @@ class SafeBrowsingResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart index 763455f1e..f91cc5957 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart @@ -74,20 +74,44 @@ class SafeBrowsingThreat { return null; } + /// Gets a possible [SafeBrowsingThreat] instance value with name [name]. + /// + /// Goes through [SafeBrowsingThreat.values] looking for a value with + /// name [name], as reported by [SafeBrowsingThreat.name]. + /// Returns the first value with the given name, otherwise `null`. + static SafeBrowsingThreat? byName(String? name) { + if (name != null) { + try { + return SafeBrowsingThreat.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SafeBrowsingThreat] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SafeBrowsingThreat.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'SAFE_BROWSING_THREAT_BILLING'; @@ -102,4 +126,15 @@ class SafeBrowsingThreat { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart index ca22db314..e946bc67d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart @@ -88,6 +88,34 @@ class Sandbox { Sandbox.ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION, ].toSet(); + /// Gets a possible [Sandbox] instance value with name [name]. + /// + /// Goes through [Sandbox.values] looking for a value with + /// name [name], as reported by [Sandbox.name]. + /// Returns the first value with the given name, otherwise `null`. + static Sandbox? byName(String? name) { + if (name != null) { + try { + return Sandbox.values.firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [Sandbox] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in Sandbox.values) value.name(): value + }; + ///Gets a possible [Sandbox] instance from a native value. static Sandbox? fromNativeValue(String? value) { if (value == null) { @@ -129,6 +157,41 @@ class Sandbox { ///Gets [String?] native value. String? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALLOW_ALL'; + case 'allow-downloads': + return 'ALLOW_DOWNLOADS'; + case 'allow-forms': + return 'ALLOW_FORMS'; + case 'allow-modals': + return 'ALLOW_MODALS'; + case 'null': + return 'ALLOW_NONE'; + case 'allow-orientation-lock': + return 'ALLOW_ORIENTATION_LOCK'; + case 'allow-pointer-lock': + return 'ALLOW_POINTER_LOCK'; + case 'allow-popups': + return 'ALLOW_POPUPS'; + case 'allow-popups-to-escape-sandbox': + return 'ALLOW_POPUPS_TO_ESCAPE_SANDBOX'; + case 'allow-presentation': + return 'ALLOW_PRESENTATION'; + case 'allow-same-origin': + return 'ALLOW_SAME_ORIGIN'; + case 'allow-scripts': + return 'ALLOW_SCRIPTS'; + case 'allow-top-navigation': + return 'ALLOW_TOP_NAVIGATION'; + case 'allow-top-navigation-by-user-activation': + return 'ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart index ce033a583..fe6b18137 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'in_app_webview_rect.dart'; import 'compress_format.dart'; +import 'enum_method.dart'; part 'screenshot_configuration.g.dart'; @@ -12,6 +13,12 @@ class ScreenshotConfiguration_ { ///The portion of your web view to capture, specified as a rectangle in the view’s coordinate system. ///The default value of this property is `null`, which captures everything in the view’s bounds rectangle. ///If you specify a custom rectangle, it must lie within the bounds rectangle of the `WebView` object. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) InAppWebViewRect_? rect; ///The width of the captured image, in points. @@ -19,14 +26,28 @@ class ScreenshotConfiguration_ { ///The web view maintains the aspect ratio of the captured content, but scales it to match the width you specify. /// ///The default value of this property is `null`, which returns an image whose size matches the original size of the captured rectangle. + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) double? snapshotWidth; ///The compression format of the captured image. ///The default value is [CompressFormat.PNG]. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) CompressFormat_ compressFormat; ///Hint to the compressor, `0-100`. The value is interpreted differently depending on the [CompressFormat]. ///[CompressFormat.PNG] is lossless, so this value is ignored. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) int quality; ///Use [afterScreenUpdates] instead. @@ -36,10 +57,10 @@ class ScreenshotConfiguration_ { ///A Boolean value that indicates whether to take the snapshot after incorporating any pending screen updates. ///The default value of this property is `true`, which causes the web view to incorporate any recent changes to the view’s content and then generate the snapshot. ///If you change the value to `false`, the `WebView` takes the snapshot immediately, and before incorporating any new changes. - /// - ///**NOTE**: available only on iOS. - /// - ///**NOTE for iOS**: Available from iOS 13.0+. + @SupportedPlatforms(platforms: [ + IOSPlatform(available: '13.0'), + MacOSPlatform(available: '10.15'), + ]) bool afterScreenUpdates; @ExchangeableObjectConstructor() diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart index 1a22c107f..b25b22637 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart @@ -12,13 +12,19 @@ class ScreenshotConfiguration { ///The default value of this property is `true`, which causes the web view to incorporate any recent changes to the view’s content and then generate the snapshot. ///If you change the value to `false`, the `WebView` takes the snapshot immediately, and before incorporating any new changes. /// - ///**NOTE**: available only on iOS. - /// - ///**NOTE for iOS**: Available from iOS 13.0+. + ///**Officially Supported Platforms/Implementations**: + ///- iOS 13.0+ + ///- MacOS 10.15+ bool afterScreenUpdates; ///The compression format of the captured image. ///The default value is [CompressFormat.PNG]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows CompressFormat compressFormat; ///Use [afterScreenUpdates] instead. @@ -27,11 +33,23 @@ class ScreenshotConfiguration { ///Hint to the compressor, `0-100`. The value is interpreted differently depending on the [CompressFormat]. ///[CompressFormat.PNG] is lossless, so this value is ignored. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows int quality; ///The portion of your web view to capture, specified as a rectangle in the view’s coordinate system. ///The default value of this property is `null`, which captures everything in the view’s bounds rectangle. ///If you specify a custom rectangle, it must lie within the bounds rectangle of the `WebView` object. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows InAppWebViewRect? rect; ///The width of the captured image, in points. @@ -39,6 +57,11 @@ class ScreenshotConfiguration { ///The web view maintains the aspect ratio of the captured content, but scales it to match the width you specify. /// ///The default value of this property is `null`, which returns an image whose size matches the original size of the captured rectangle. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS double? snapshotWidth; ScreenshotConfiguration( {this.rect, @@ -54,29 +77,45 @@ class ScreenshotConfiguration { } ///Gets a possible [ScreenshotConfiguration] instance from a [Map] value. - static ScreenshotConfiguration? fromMap(Map? map) { + static ScreenshotConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ScreenshotConfiguration( iosAfterScreenUpdates: map['afterScreenUpdates'], - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), snapshotWidth: map['snapshotWidth'], ); - instance.afterScreenUpdates = map['afterScreenUpdates']; - instance.compressFormat = - CompressFormat.fromNativeValue(map['compressFormat'])!; - instance.quality = map['quality']; + if (map['afterScreenUpdates'] != null) { + instance.afterScreenUpdates = map['afterScreenUpdates']; + } + if (map['compressFormat'] != null) { + instance.compressFormat = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CompressFormat.fromNativeValue(map['compressFormat']), + EnumMethod.value => CompressFormat.fromValue(map['compressFormat']), + EnumMethod.name => CompressFormat.byName(map['compressFormat']) + }!; + } + if (map['quality'] != null) { + instance.quality = map['quality']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "afterScreenUpdates": afterScreenUpdates, - "compressFormat": compressFormat.toNativeValue(), + "compressFormat": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => compressFormat.toNativeValue(), + EnumMethod.value => compressFormat.toValue(), + EnumMethod.name => compressFormat.name() + }, "quality": quality, - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), "snapshotWidth": snapshotWidth, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart index 05a40266f..583ef1283 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'cross_origin.dart'; import 'referrer_policy.dart'; +import 'enum_method.dart'; part 'script_html_tag_attributes.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart index 4ca725079..0d13f3fc7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart @@ -86,35 +86,56 @@ class ScriptHtmlTagAttributes { } ///Gets a possible [ScriptHtmlTagAttributes] instance from a [Map] value. - static ScriptHtmlTagAttributes? fromMap(Map? map) { + static ScriptHtmlTagAttributes? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ScriptHtmlTagAttributes( async: map['async'], - crossOrigin: CrossOrigin.fromNativeValue(map['crossOrigin']), + crossOrigin: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CrossOrigin.fromNativeValue(map['crossOrigin']), + EnumMethod.value => CrossOrigin.fromValue(map['crossOrigin']), + EnumMethod.name => CrossOrigin.byName(map['crossOrigin']) + }, defer: map['defer'], id: map['id'], integrity: map['integrity'], noModule: map['noModule'], nonce: map['nonce'], - referrerPolicy: ReferrerPolicy.fromNativeValue(map['referrerPolicy']), + referrerPolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ReferrerPolicy.fromNativeValue(map['referrerPolicy']), + EnumMethod.value => ReferrerPolicy.fromValue(map['referrerPolicy']), + EnumMethod.name => ReferrerPolicy.byName(map['referrerPolicy']) + }, ); - instance.type = map['type']; + if (map['type'] != null) { + instance.type = map['type']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "async": async, - "crossOrigin": crossOrigin?.toNativeValue(), + "crossOrigin": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => crossOrigin?.toNativeValue(), + EnumMethod.value => crossOrigin?.toValue(), + EnumMethod.name => crossOrigin?.name() + }, "defer": defer, "id": id, "integrity": integrity, "noModule": noModule, "nonce": nonce, - "referrerPolicy": referrerPolicy?.toNativeValue(), + "referrerPolicy": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => referrerPolicy?.toNativeValue(), + EnumMethod.value => referrerPolicy?.toValue(), + EnumMethod.name => referrerPolicy?.name() + }, "type": type, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart index 871f46754..7acdeb53d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart @@ -75,20 +75,43 @@ class ScrollBarStyle { return null; } + /// Gets a possible [ScrollBarStyle] instance value with name [name]. + /// + /// Goes through [ScrollBarStyle.values] looking for a value with + /// name [name], as reported by [ScrollBarStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollBarStyle? byName(String? name) { + if (name != null) { + try { + return ScrollBarStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollBarStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in ScrollBarStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 16777216: return 'SCROLLBARS_INSIDE_INSET'; @@ -101,6 +124,17 @@ class ScrollBarStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the style of the scrollbars. @@ -176,20 +210,44 @@ class AndroidScrollBarStyle { return null; } + /// Gets a possible [AndroidScrollBarStyle] instance value with name [name]. + /// + /// Goes through [AndroidScrollBarStyle.values] looking for a value with + /// name [name], as reported by [AndroidScrollBarStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidScrollBarStyle? byName(String? name) { + if (name != null) { + try { + return AndroidScrollBarStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidScrollBarStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidScrollBarStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 16777216: return 'SCROLLBARS_INSIDE_INSET'; @@ -202,4 +260,15 @@ class AndroidScrollBarStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart index c53faec73..7b2335906 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart @@ -66,20 +66,45 @@ class ScrollViewContentInsetAdjustmentBehavior { return null; } + /// Gets a possible [ScrollViewContentInsetAdjustmentBehavior] instance value with name [name]. + /// + /// Goes through [ScrollViewContentInsetAdjustmentBehavior.values] looking for a value with + /// name [name], as reported by [ScrollViewContentInsetAdjustmentBehavior.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollViewContentInsetAdjustmentBehavior? byName(String? name) { + if (name != null) { + try { + return ScrollViewContentInsetAdjustmentBehavior.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollViewContentInsetAdjustmentBehavior] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ScrollViewContentInsetAdjustmentBehavior.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -92,6 +117,17 @@ class ScrollViewContentInsetAdjustmentBehavior { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to configure how safe area insets are added to the adjusted content inset. @@ -162,20 +198,46 @@ class IOSUIScrollViewContentInsetAdjustmentBehavior { return null; } + /// Gets a possible [IOSUIScrollViewContentInsetAdjustmentBehavior] instance value with name [name]. + /// + /// Goes through [IOSUIScrollViewContentInsetAdjustmentBehavior.values] looking for a value with + /// name [name], as reported by [IOSUIScrollViewContentInsetAdjustmentBehavior.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIScrollViewContentInsetAdjustmentBehavior? byName(String? name) { + if (name != null) { + try { + return IOSUIScrollViewContentInsetAdjustmentBehavior.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIScrollViewContentInsetAdjustmentBehavior] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map + asNameMap() => { + for (final value + in IOSUIScrollViewContentInsetAdjustmentBehavior.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -188,4 +250,15 @@ class IOSUIScrollViewContentInsetAdjustmentBehavior { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart index 2f64bd585..291579327 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart @@ -55,12 +55,54 @@ class ScrollViewDecelerationRate { return null; } + /// Gets a possible [ScrollViewDecelerationRate] instance value with name [name]. + /// + /// Goes through [ScrollViewDecelerationRate.values] looking for a value with + /// name [name], as reported by [ScrollViewDecelerationRate.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollViewDecelerationRate? byName(String? name) { + if (name != null) { + try { + return ScrollViewDecelerationRate.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollViewDecelerationRate] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ScrollViewDecelerationRate.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'FAST': + return 'FAST'; + case 'NORMAL': + return 'NORMAL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -125,12 +167,54 @@ class IOSUIScrollViewDecelerationRate { return null; } + /// Gets a possible [IOSUIScrollViewDecelerationRate] instance value with name [name]. + /// + /// Goes through [IOSUIScrollViewDecelerationRate.values] looking for a value with + /// name [name], as reported by [IOSUIScrollViewDecelerationRate.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIScrollViewDecelerationRate? byName(String? name) { + if (name != null) { + try { + return IOSUIScrollViewDecelerationRate.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIScrollViewDecelerationRate] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIScrollViewDecelerationRate.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'FAST': + return 'FAST'; + case 'NORMAL': + return 'NORMAL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart index 0f043309e..292de50ab 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart @@ -58,20 +58,44 @@ class SearchResultDisplayStyle { return null; } + /// Gets a possible [SearchResultDisplayStyle] instance value with name [name]. + /// + /// Goes through [SearchResultDisplayStyle.values] looking for a value with + /// name [name], as reported by [SearchResultDisplayStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static SearchResultDisplayStyle? byName(String? name) { + if (name != null) { + try { + return SearchResultDisplayStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SearchResultDisplayStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SearchResultDisplayStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'CURRENT_AND_TOTAL'; @@ -82,4 +106,15 @@ class SearchResultDisplayStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart index 958761f01..669077024 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'security_origin.g.dart'; ///An object that identifies the origin of a particular resource. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart index 4d0b53415..0afa630e7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart @@ -20,7 +20,8 @@ class SecurityOrigin { {required this.host, required this.port, required this.protocol}); ///Gets a possible [SecurityOrigin] instance from a [Map] value. - static SecurityOrigin? fromMap(Map? map) { + static SecurityOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class SecurityOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "host": host, "port": port, @@ -71,7 +72,8 @@ class IOSWKSecurityOrigin { {required this.host, required this.port, required this.protocol}); ///Gets a possible [IOSWKSecurityOrigin] instance from a [Map] value. - static IOSWKSecurityOrigin? fromMap(Map? map) { + static IOSWKSecurityOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -84,7 +86,7 @@ class IOSWKSecurityOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "host": host, "port": port, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart index 43acdd425..4944cbe71 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart @@ -54,12 +54,53 @@ class SelectionGranularity { return null; } + /// Gets a possible [SelectionGranularity] instance value with name [name]. + /// + /// Goes through [SelectionGranularity.values] looking for a value with + /// name [name], as reported by [SelectionGranularity.name]. + /// Returns the first value with the given name, otherwise `null`. + static SelectionGranularity? byName(String? name) { + if (name != null) { + try { + return SelectionGranularity.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SelectionGranularity] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SelectionGranularity.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CHARACTER'; + case 0: + return 'DYNAMIC'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,13 +109,7 @@ class SelectionGranularity { @override String toString() { - switch (_value) { - case 1: - return 'CHARACTER'; - case 0: - return 'DYNAMIC'; - } - return _value.toString(); + return name(); } } @@ -128,12 +163,54 @@ class IOSWKSelectionGranularity { return null; } + /// Gets a possible [IOSWKSelectionGranularity] instance value with name [name]. + /// + /// Goes through [IOSWKSelectionGranularity.values] looking for a value with + /// name [name], as reported by [IOSWKSelectionGranularity.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKSelectionGranularity? byName(String? name) { + if (name != null) { + try { + return IOSWKSelectionGranularity.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKSelectionGranularity] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKSelectionGranularity.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CHARACTER'; + case 0: + return 'DYNAMIC'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -142,12 +219,6 @@ class IOSWKSelectionGranularity { @override String toString() { - switch (_value) { - case 1: - return 'CHARACTER'; - case 0: - return 'DYNAMIC'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart index acfad82e5..e03d1e15a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'server_trust_auth_response_action.dart'; +import 'enum_method.dart'; part 'server_trust_auth_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart index 4975dd74a..fdd3840a5 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart @@ -13,20 +13,30 @@ class ServerTrustAuthResponse { ServerTrustAuthResponse({this.action = ServerTrustAuthResponseAction.CANCEL}); ///Gets a possible [ServerTrustAuthResponse] instance from a [Map] value. - static ServerTrustAuthResponse? fromMap(Map? map) { + static ServerTrustAuthResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ServerTrustAuthResponse(); - instance.action = - ServerTrustAuthResponseAction.fromNativeValue(map['action']); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ServerTrustAuthResponseAction.fromNativeValue(map['action']), + EnumMethod.value => + ServerTrustAuthResponseAction.fromValue(map['action']), + EnumMethod.name => ServerTrustAuthResponseAction.byName(map['action']) + }; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart index 70ac9817a..ebfdccf65 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart @@ -54,12 +54,54 @@ class ServerTrustAuthResponseAction { return null; } + /// Gets a possible [ServerTrustAuthResponseAction] instance value with name [name]. + /// + /// Goes through [ServerTrustAuthResponseAction.values] looking for a value with + /// name [name], as reported by [ServerTrustAuthResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static ServerTrustAuthResponseAction? byName(String? name) { + if (name != null) { + try { + return ServerTrustAuthResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ServerTrustAuthResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ServerTrustAuthResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'CANCEL'; + case 1: + return 'PROCEED'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +110,6 @@ class ServerTrustAuthResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'CANCEL'; - case 1: - return 'PROCEED'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart index a1c66d596..86dd52b98 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_authentication_challenge.dart'; import 'url_protection_space.dart'; +import 'enum_method.dart'; part 'server_trust_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart index 9db07d9cc..9b12ef419 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart @@ -13,21 +13,23 @@ class ServerTrustChallenge extends URLAuthenticationChallenge { : super(protectionSpace: protectionSpace); ///Gets a possible [ServerTrustChallenge] instance from a [Map] value. - static ServerTrustChallenge? fromMap(Map? map) { + static ServerTrustChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ServerTrustChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart index 378e44959..c41616a30 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart @@ -56,12 +56,54 @@ class ShouldAllowDeprecatedTLSAction { return null; } + /// Gets a possible [ShouldAllowDeprecatedTLSAction] instance value with name [name]. + /// + /// Goes through [ShouldAllowDeprecatedTLSAction.values] looking for a value with + /// name [name], as reported by [ShouldAllowDeprecatedTLSAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static ShouldAllowDeprecatedTLSAction? byName(String? name) { + if (name != null) { + try { + return ShouldAllowDeprecatedTLSAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ShouldAllowDeprecatedTLSAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ShouldAllowDeprecatedTLSAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -70,13 +112,7 @@ class ShouldAllowDeprecatedTLSAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } @@ -132,12 +168,54 @@ class IOSShouldAllowDeprecatedTLSAction { return null; } + /// Gets a possible [IOSShouldAllowDeprecatedTLSAction] instance value with name [name]. + /// + /// Goes through [IOSShouldAllowDeprecatedTLSAction.values] looking for a value with + /// name [name], as reported by [IOSShouldAllowDeprecatedTLSAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSShouldAllowDeprecatedTLSAction? byName(String? name) { + if (name != null) { + try { + return IOSShouldAllowDeprecatedTLSAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSShouldAllowDeprecatedTLSAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSShouldAllowDeprecatedTLSAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -146,12 +224,6 @@ class IOSShouldAllowDeprecatedTLSAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart new file mode 100644 index 000000000..4cad89af7 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart @@ -0,0 +1,39 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import '../in_app_webview/platform_webview.dart'; +import 'show_file_chooser_request_mode.dart'; +import 'enum_method.dart'; + +part 'show_file_chooser_request.g.dart'; + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +@ExchangeableObject() +class ShowFileChooserRequest_ { + ///The file chooser mode. + final ShowFileChooserRequestMode_ mode; + + ///An array of acceptable MIME types. + ///The returned MIME type could be partial such as audio/*. + ///The array will be empty if no acceptable types are specified. + final List acceptTypes; + + ///Preference for a live media captured value (e. g. Camera, Microphone). + ///True indicates capture is enabled, false disabled. + ///Use [acceptTypes] to determine suitable capture devices. + final bool isCaptureEnabled; + + ///The title to use for this file selector. + ///If `null` a default title should be used. + final String? title; + + ///The file name of a default selection if specified, or `null`. + final String? filenameHint; + + ShowFileChooserRequest_({ + required this.mode, + required this.acceptTypes, + required this.isCaptureEnabled, + this.title, + this.filenameHint, + }); +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart new file mode 100644 index 000000000..14d951687 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart @@ -0,0 +1,82 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_request.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +class ShowFileChooserRequest { + ///An array of acceptable MIME types. + ///The returned MIME type could be partial such as audio/*. + ///The array will be empty if no acceptable types are specified. + final List acceptTypes; + + ///The file name of a default selection if specified, or `null`. + final String? filenameHint; + + ///Preference for a live media captured value (e. g. Camera, Microphone). + ///True indicates capture is enabled, false disabled. + ///Use [acceptTypes] to determine suitable capture devices. + final bool isCaptureEnabled; + + ///The file chooser mode. + final ShowFileChooserRequestMode mode; + + ///The title to use for this file selector. + ///If `null` a default title should be used. + final String? title; + ShowFileChooserRequest( + {required this.acceptTypes, + this.filenameHint, + required this.isCaptureEnabled, + required this.mode, + this.title}); + + ///Gets a possible [ShowFileChooserRequest] instance from a [Map] value. + static ShowFileChooserRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ShowFileChooserRequest( + acceptTypes: List.from(map['acceptTypes']!.cast()), + filenameHint: map['filenameHint'], + isCaptureEnabled: map['isCaptureEnabled'], + mode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ShowFileChooserRequestMode.fromNativeValue(map['mode']), + EnumMethod.value => ShowFileChooserRequestMode.fromValue(map['mode']), + EnumMethod.name => ShowFileChooserRequestMode.byName(map['mode']) + }!, + title: map['title'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "acceptTypes": acceptTypes, + "filenameHint": filenameHint, + "isCaptureEnabled": isCaptureEnabled, + "mode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => mode.toNativeValue(), + EnumMethod.value => mode.toValue(), + EnumMethod.name => mode.name() + }, + "title": title, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ShowFileChooserRequest{acceptTypes: $acceptTypes, filenameHint: $filenameHint, isCaptureEnabled: $isCaptureEnabled, mode: $mode, title: $title}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart new file mode 100644 index 000000000..dff40f408 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart @@ -0,0 +1,41 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'show_file_chooser_request.dart'; + +part 'show_file_chooser_request_mode.g.dart'; + +///It represents file chooser mode of a [ShowFileChooserRequest]. +@ExchangeableEnum() +class ShowFileChooserRequestMode_ { + // ignore: unused_field + final int _value; + // ignore: unused_field + final int? _nativeValue = null; + const ShowFileChooserRequestMode_._internal(this._value); + + ///Open single file. Requires that the file exists before allowing the user to pick it. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 0) + ]) + static const OPEN = const ShowFileChooserRequestMode_._internal(0); + + ///Like Open but allows multiple files to be selected. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 1) + ]) + static const OPEN_MULTIPLE = const ShowFileChooserRequestMode_._internal(1); + + ///Like Open but allows a folder to be selected. + ///The implementation should enumerate all files selected by this operation. + ///This feature is not supported at the moment. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 2) + ]) + static const OPEN_FOLDER = const ShowFileChooserRequestMode_._internal(2); + + ///Allows picking a nonexistent file and saving it. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 3) + ]) + static const SAVE = const ShowFileChooserRequestMode_._internal(3); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart new file mode 100644 index 000000000..2785b69b7 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_request_mode.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///It represents file chooser mode of a [ShowFileChooserRequest]. +class ShowFileChooserRequestMode { + final int _value; + final int? _nativeValue; + const ShowFileChooserRequestMode._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ShowFileChooserRequestMode._internalMultiPlatform( + int value, Function nativeValue) => + ShowFileChooserRequestMode._internal(value, nativeValue()); + + ///Open single file. Requires that the file exists before allowing the user to pick it. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN = ShowFileChooserRequestMode._internalMultiPlatform(0, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 0; + default: + break; + } + return null; + }); + + ///Like Open but allows a folder to be selected. + ///The implementation should enumerate all files selected by this operation. + ///This feature is not supported at the moment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN_FOLDER = + ShowFileChooserRequestMode._internalMultiPlatform(2, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 2; + default: + break; + } + return null; + }); + + ///Like Open but allows multiple files to be selected. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN_MULTIPLE = + ShowFileChooserRequestMode._internalMultiPlatform(1, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 1; + default: + break; + } + return null; + }); + + ///Allows picking a nonexistent file and saving it. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final SAVE = ShowFileChooserRequestMode._internalMultiPlatform(3, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 3; + default: + break; + } + return null; + }); + + ///Set of all values of [ShowFileChooserRequestMode]. + static final Set values = [ + ShowFileChooserRequestMode.OPEN, + ShowFileChooserRequestMode.OPEN_FOLDER, + ShowFileChooserRequestMode.OPEN_MULTIPLE, + ShowFileChooserRequestMode.SAVE, + ].toSet(); + + ///Gets a possible [ShowFileChooserRequestMode] instance from [int] value. + static ShowFileChooserRequestMode? fromValue(int? value) { + if (value != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ShowFileChooserRequestMode] instance from a native value. + static ShowFileChooserRequestMode? fromNativeValue(int? value) { + if (value != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ShowFileChooserRequestMode] instance value with name [name]. + /// + /// Goes through [ShowFileChooserRequestMode.values] looking for a value with + /// name [name], as reported by [ShowFileChooserRequestMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static ShowFileChooserRequestMode? byName(String? name) { + if (name != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ShowFileChooserRequestMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ShowFileChooserRequestMode.values) + value.name(): value + }; + + ///Gets [int] value. + int toValue() => _value; + + ///Gets [int?] native value. + int? toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'OPEN'; + case 2: + return 'OPEN_FOLDER'; + case 1: + return 'OPEN_MULTIPLE'; + case 3: + return 'SAVE'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart new file mode 100644 index 000000000..88fc3ee7c --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart @@ -0,0 +1,22 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + +part 'show_file_chooser_response.g.dart'; + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +@ExchangeableObject() +class ShowFileChooserResponse_ { + ///Whether the file chooser request was handled by the client. + final bool handledByClient; + + ///The file paths of the selected files or `null` to cancel the request. + ///Each file path must be a valid file URI using the "file:" scheme. + final List? filePaths; + + ShowFileChooserResponse_({ + required this.handledByClient, + this.filePaths, + }); +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart new file mode 100644 index 000000000..66eeb7dea --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_response.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +class ShowFileChooserResponse { + ///The file paths of the selected files or `null` to cancel the request. + ///Each file path must be a valid file URI using the "file:" scheme. + final List? filePaths; + + ///Whether the file chooser request was handled by the client. + final bool handledByClient; + ShowFileChooserResponse({this.filePaths, required this.handledByClient}); + + ///Gets a possible [ShowFileChooserResponse] instance from a [Map] value. + static ShowFileChooserResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ShowFileChooserResponse( + filePaths: map['filePaths'] != null + ? List.from(map['filePaths']!.cast()) + : null, + handledByClient: map['handledByClient'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "filePaths": filePaths, + "handledByClient": handledByClient, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ShowFileChooserResponse{filePaths: $filePaths, handledByClient: $handledByClient}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart index 041cfabd4..35c3a3a22 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../util.dart'; import '../x509_certificate/x509_certificate.dart'; import '../x509_certificate/asn1_distinguished_names.dart'; +import 'enum_method.dart'; import 'ssl_certificate_dname.dart'; @@ -34,7 +35,8 @@ class SslCertificate_ { this.x509Certificate}); ///Gets a possible [SslCertificate] instance from a [Map] value. - static SslCertificate? fromMap(Map? map) { + static SslCertificate? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -81,10 +83,12 @@ class SslCertificate_ { } return SslCertificate( - issuedBy: - SslCertificateDName.fromMap(map["issuedBy"]?.cast()), - issuedTo: - SslCertificateDName.fromMap(map["issuedTo"]?.cast()), + issuedBy: SslCertificateDName.fromMap( + map["issuedBy"]?.cast(), + enumMethod: enumMethod), + issuedTo: SslCertificateDName.fromMap( + map["issuedTo"]?.cast(), + enumMethod: enumMethod), validNotAfterDate: map["validNotAfterDate"] != null ? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]) : null, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart index d98b03127..8d548c6f8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart @@ -30,7 +30,8 @@ class SslCertificate { this.x509Certificate}); ///Gets a possible [SslCertificate] instance from a [Map] value. - static SslCertificate? fromMap(Map? map) { + static SslCertificate? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -72,9 +73,11 @@ class SslCertificate { } return SslCertificate( issuedBy: SslCertificateDName.fromMap( - map["issuedBy"]?.cast()), + map["issuedBy"]?.cast(), + enumMethod: enumMethod), issuedTo: SslCertificateDName.fromMap( - map["issuedTo"]?.cast()), + map["issuedTo"]?.cast(), + enumMethod: enumMethod), validNotAfterDate: map["validNotAfterDate"] != null ? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]) : null, @@ -85,13 +88,13 @@ class SslCertificate { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "issuedBy": issuedBy?.toMap(), - "issuedTo": issuedTo?.toMap(), + "issuedBy": issuedBy?.toMap(enumMethod: enumMethod), + "issuedTo": issuedTo?.toMap(enumMethod: enumMethod), "validNotAfterDate": validNotAfterDate?.millisecondsSinceEpoch, "validNotBeforeDate": validNotBeforeDate?.millisecondsSinceEpoch, - "x509Certificate": x509Certificate?.toMap(), + "x509Certificate": x509Certificate?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart index fba647b10..79822a0fb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'ssl_certificate.dart'; part 'ssl_certificate_dname.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart index 3231eb69c..b6ac1c5c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart @@ -23,7 +23,8 @@ class SslCertificateDName { {this.CName = "", this.DName = "", this.OName = "", this.UName = ""}); ///Gets a possible [SslCertificateDName] instance from a [Map] value. - static SslCertificateDName? fromMap(Map? map) { + static SslCertificateDName? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -36,7 +37,7 @@ class SslCertificateDName { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "CName": CName, "DName": DName, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart index 1971d8405..4a3d73923 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'ssl_error_type.dart'; part 'ssl_error.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart index 1f838bc3c..1aeec754b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart @@ -32,23 +32,40 @@ class SslError { } ///Gets a possible [SslError] instance from a [Map] value. - static SslError? fromMap(Map? map) { + static SslError? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = SslError( - androidError: AndroidSslError.fromNativeValue(map['code']), - code: SslErrorType.fromNativeValue(map['code']), - iosError: IOSSslError.fromNativeValue(map['code']), + androidError: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => AndroidSslError.fromNativeValue(map['code']), + EnumMethod.value => AndroidSslError.fromValue(map['code']), + EnumMethod.name => AndroidSslError.byName(map['code']) + }, + code: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => SslErrorType.fromNativeValue(map['code']), + EnumMethod.value => SslErrorType.fromValue(map['code']), + EnumMethod.name => SslErrorType.byName(map['code']) + }, + iosError: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSSslError.fromNativeValue(map['code']), + EnumMethod.value => IOSSslError.fromValue(map['code']), + EnumMethod.name => IOSSslError.byName(map['code']) + }, message: map['message'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "code": code?.toNativeValue(), + "code": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => code?.toNativeValue(), + EnumMethod.value => code?.toValue(), + EnumMethod.name => code?.name() + }, "message": message, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart index b4db1bd57..a8348cdb3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart @@ -32,7 +32,12 @@ class SslErrorType_ { apiName: 'SslError.SSL_EXPIRED', apiUrl: 'https://developer.android.com/reference/android/net/http/SslError#SSL_EXPIRED', - value: 1) + value: 1), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 2) ]) static const EXPIRED = SslErrorType_._internal('EXPIRED'); @@ -82,7 +87,12 @@ class SslErrorType_ { apiName: 'SecTrustResultType.invalid', apiUrl: 'https://developer.apple.com/documentation/security/sectrustresulttype/invalid', - value: 0) + value: 0), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 5) ]) static const INVALID = SslErrorType_._internal('INVALID'); @@ -176,7 +186,9 @@ class SslErrorType_ { ///Indicates a failure other than that of trust evaluation. /// ///This value indicates that evaluation failed for some other reason. - ///This can be caused by either a revoked certificate or by OS-level errors that are unrelated to the certificates themselves. + /// + ///On iOS and macOS, this can be caused by either a revoked certificate or + ///by OS-level errors that are unrelated to the certificates themselves. @EnumSupportedPlatforms(platforms: [ EnumIOSPlatform( apiName: 'SecTrustResultType.otherError', @@ -187,9 +199,37 @@ class SslErrorType_ { apiName: 'SecTrustResultType.otherError', apiUrl: 'https://developer.apple.com/documentation/security/sectrustresulttype/othererror', - value: 7) + value: 7), + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 3) ]) static const OTHER_ERROR = SslErrorType_._internal('OTHER_ERROR'); + + ///Indicates that the SSL certificate common name does not match the web address. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 1) + ]) + static const COMMON_NAME_IS_INCORRECT = + SslErrorType_._internal('COMMON_NAME_IS_INCORRECT'); + + ///Indicates that the SSL certificate has been revoked. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 4) + ]) + static const REVOKED = SslErrorType_._internal('REVOKED'); } ///Class that represents the Android-specific primary error associated to the server SSL certificate. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart index e4043d9fc..ba0d2204f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart @@ -17,6 +17,21 @@ class SslErrorType { String value, Function nativeValue) => SslErrorType._internal(value, nativeValue()); + ///Indicates that the SSL certificate common name does not match the web address. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) + static final COMMON_NAME_IS_INCORRECT = + SslErrorType._internalMultiPlatform('COMMON_NAME_IS_INCORRECT', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + ///The date of the certificate is invalid. /// ///**Officially Supported Platforms/Implementations**: @@ -58,10 +73,13 @@ class SslErrorType { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - SslError.SSL_EXPIRED](https://developer.android.com/reference/android/net/http/SslError#SSL_EXPIRED)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final EXPIRED = SslErrorType._internalMultiPlatform('EXPIRED', () { switch (defaultTargetPlatform) { case TargetPlatform.android: return 1; + case TargetPlatform.windows: + return 2; default: break; } @@ -112,6 +130,7 @@ class SslErrorType { ///- Android native WebView ([Official API - SslError.SSL_INVALID](https://developer.android.com/reference/android/net/http/SslError#SSL_INVALID)) ///- iOS ([Official API - SecTrustResultType.invalid](https://developer.apple.com/documentation/security/sectrustresulttype/invalid)) ///- MacOS ([Official API - SecTrustResultType.invalid](https://developer.apple.com/documentation/security/sectrustresulttype/invalid)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final INVALID = SslErrorType._internalMultiPlatform('INVALID', () { switch (defaultTargetPlatform) { case TargetPlatform.android: @@ -120,6 +139,8 @@ class SslErrorType { return 0; case TargetPlatform.macOS: return 0; + case TargetPlatform.windows: + return 5; default: break; } @@ -144,11 +165,14 @@ class SslErrorType { ///Indicates a failure other than that of trust evaluation. /// ///This value indicates that evaluation failed for some other reason. - ///This can be caused by either a revoked certificate or by OS-level errors that are unrelated to the certificates themselves. + /// + ///On iOS and macOS, this can be caused by either a revoked certificate or + ///by OS-level errors that are unrelated to the certificates themselves. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - SecTrustResultType.otherError](https://developer.apple.com/documentation/security/sectrustresulttype/othererror)) ///- MacOS ([Official API - SecTrustResultType.otherError](https://developer.apple.com/documentation/security/sectrustresulttype/othererror)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final OTHER_ERROR = SslErrorType._internalMultiPlatform('OTHER_ERROR', () { switch (defaultTargetPlatform) { @@ -156,6 +180,8 @@ class SslErrorType { return 7; case TargetPlatform.macOS: return 7; + case TargetPlatform.windows: + return 3; default: break; } @@ -188,6 +214,20 @@ class SslErrorType { return null; }); + ///Indicates that the SSL certificate has been revoked. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) + static final REVOKED = SslErrorType._internalMultiPlatform('REVOKED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + ///Indicates the evaluation succeeded and the certificate is implicitly trusted, but user intent was not explicitly specified. /// ///This value indicates that evaluation reached an (implicitly trusted) anchor certificate without any evaluation failures, @@ -229,6 +269,7 @@ class SslErrorType { ///Set of all values of [SslErrorType]. static final Set values = [ + SslErrorType.COMMON_NAME_IS_INCORRECT, SslErrorType.DATE_INVALID, SslErrorType.DENY, SslErrorType.EXPIRED, @@ -238,6 +279,7 @@ class SslErrorType { SslErrorType.NOT_YET_VALID, SslErrorType.OTHER_ERROR, SslErrorType.RECOVERABLE_TRUST_FAILURE, + SslErrorType.REVOKED, SslErrorType.UNSPECIFIED, SslErrorType.UNTRUSTED, ].toSet(); @@ -268,12 +310,74 @@ class SslErrorType { return null; } + /// Gets a possible [SslErrorType] instance value with name [name]. + /// + /// Goes through [SslErrorType.values] looking for a value with + /// name [name], as reported by [SslErrorType.name]. + /// Returns the first value with the given name, otherwise `null`. + static SslErrorType? byName(String? name) { + if (name != null) { + try { + return SslErrorType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SslErrorType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in SslErrorType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'COMMON_NAME_IS_INCORRECT': + return 'COMMON_NAME_IS_INCORRECT'; + case 'DATE_INVALID': + return 'DATE_INVALID'; + case 'DENY': + return 'DENY'; + case 'EXPIRED': + return 'EXPIRED'; + case 'FATAL_TRUST_FAILURE': + return 'FATAL_TRUST_FAILURE'; + case 'IDMISMATCH': + return 'IDMISMATCH'; + case 'INVALID': + return 'INVALID'; + case 'NOT_YET_VALID': + return 'NOT_YET_VALID'; + case 'OTHER_ERROR': + return 'OTHER_ERROR'; + case 'RECOVERABLE_TRUST_FAILURE': + return 'RECOVERABLE_TRUST_FAILURE'; + case 'REVOKED': + return 'REVOKED'; + case 'UNSPECIFIED': + return 'UNSPECIFIED'; + case 'UNTRUSTED': + return 'UNTRUSTED'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -353,20 +457,43 @@ class AndroidSslError { return null; } + /// Gets a possible [AndroidSslError] instance value with name [name]. + /// + /// Goes through [AndroidSslError.values] looking for a value with + /// name [name], as reported by [AndroidSslError.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidSslError? byName(String? name) { + if (name != null) { + try { + return AndroidSslError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidSslError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in AndroidSslError.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'SSL_DATE_INVALID'; @@ -383,6 +510,17 @@ class AndroidSslError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class that represents the iOS-specific primary error associated to the server SSL certificate. @@ -451,20 +589,43 @@ class IOSSslError { return null; } + /// Gets a possible [IOSSslError] instance value with name [name]. + /// + /// Goes through [IOSSslError.values] looking for a value with + /// name [name], as reported by [IOSSslError.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSSslError? byName(String? name) { + if (name != null) { + try { + return IOSSslError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSSslError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in IOSSslError.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'DENY'; @@ -481,4 +642,15 @@ class IOSSslError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart index 3487a1843..5943c03f6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart @@ -87,20 +87,43 @@ class TracingCategory { return null; } + /// Gets a possible [TracingCategory] instance value with name [name]. + /// + /// Goes through [TracingCategory.values] looking for a value with + /// name [name], as reported by [TracingCategory.name]. + /// Returns the first value with the given name, otherwise `null`. + static TracingCategory? byName(String? name) { + if (name != null) { + try { + return TracingCategory.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TracingCategory] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in TracingCategory.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'CATEGORIES_ALL'; @@ -121,4 +144,15 @@ class TracingCategory { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart index 3552fe3d1..1dab01ed6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart @@ -59,12 +59,52 @@ class TracingMode { return null; } + /// Gets a possible [TracingMode] instance value with name [name]. + /// + /// Goes through [TracingMode.values] looking for a value with + /// name [name], as reported by [TracingMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static TracingMode? byName(String? name) { + if (name != null) { + try { + return TracingMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TracingMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in TracingMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'RECORD_CONTINUOUSLY'; + case 0: + return 'RECORD_UNTIL_FULL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -73,12 +113,6 @@ class TracingMode { @override String toString() { - switch (_value) { - case 1: - return 'RECORD_CONTINUOUSLY'; - case 0: - return 'RECORD_UNTIL_FULL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart index ef160eebe..829b9b6bb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'trusted_web_activity_display_mode.dart'; part 'trusted_web_activity_default_display_mode.g.dart'; @@ -13,7 +14,7 @@ class TrustedWebActivityDefaultDisplayMode_ @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart index 395866eba..634cbd4f6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart @@ -13,14 +13,14 @@ class TrustedWebActivityDefaultDisplayMode static final String _type = "DEFAULT_MODE"; TrustedWebActivityDefaultDisplayMode(); @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart index 2b7e54ab9..97405a532 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'trusted_web_activity_display_mode.g.dart'; ///Class that represents display mode of a Trusted Web Activity. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart index 064073015..ed97b55be 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart @@ -11,7 +11,7 @@ abstract class TrustedWebActivityDisplayMode { TrustedWebActivityDisplayMode(); ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart index ead2d1898..dbddaec14 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'trusted_web_activity_display_mode.dart'; import 'layout_in_display_cutout_mode.dart'; +import 'enum_method.dart'; part 'trusted_web_activity_immersive_display_mode.g.dart'; @@ -35,7 +36,7 @@ class TrustedWebActivityImmersiveDisplayMode_ @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart index a5370c9eb..41c9f4b55 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart @@ -33,32 +33,52 @@ class TrustedWebActivityImmersiveDisplayMode ///Gets a possible [TrustedWebActivityImmersiveDisplayMode] instance from a [Map] value. static TrustedWebActivityImmersiveDisplayMode? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = TrustedWebActivityImmersiveDisplayMode( isSticky: map['isSticky'], - layoutInDisplayCutoutMode: + layoutInDisplayCutoutMode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => AndroidLayoutInDisplayCutoutMode.fromNativeValue( map['displayCutoutMode']), + EnumMethod.value => + AndroidLayoutInDisplayCutoutMode.fromValue(map['displayCutoutMode']), + EnumMethod.name => + AndroidLayoutInDisplayCutoutMode.byName(map['displayCutoutMode']) + }, ); - instance.displayCutoutMode = - LayoutInDisplayCutoutMode.fromNativeValue(map['displayCutoutMode'])!; + if (map['displayCutoutMode'] != null) { + instance.displayCutoutMode = + switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + LayoutInDisplayCutoutMode.fromNativeValue(map['displayCutoutMode']), + EnumMethod.value => + LayoutInDisplayCutoutMode.fromValue(map['displayCutoutMode']), + EnumMethod.name => + LayoutInDisplayCutoutMode.byName(map['displayCutoutMode']) + }!; + } return instance; } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "displayCutoutMode": displayCutoutMode.toNativeValue(), + "displayCutoutMode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => displayCutoutMode.toNativeValue(), + EnumMethod.value => displayCutoutMode.toValue(), + EnumMethod.name => displayCutoutMode.name() + }, "isSticky": isSticky, - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart index 1d7c4676b..f17fdc4b8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart @@ -104,20 +104,45 @@ class TrustedWebActivityScreenOrientation { return null; } + /// Gets a possible [TrustedWebActivityScreenOrientation] instance value with name [name]. + /// + /// Goes through [TrustedWebActivityScreenOrientation.values] looking for a value with + /// name [name], as reported by [TrustedWebActivityScreenOrientation.name]. + /// Returns the first value with the given name, otherwise `null`. + static TrustedWebActivityScreenOrientation? byName(String? name) { + if (name != null) { + try { + return TrustedWebActivityScreenOrientation.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TrustedWebActivityScreenOrientation] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in TrustedWebActivityScreenOrientation.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 5: return 'ANY'; @@ -140,4 +165,15 @@ class TrustedWebActivityScreenOrientation { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart index 31f16b6cb..79bb0af4c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'ui_event_attribution.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart index 3fda296e9..9b85f14ec 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart @@ -35,7 +35,8 @@ class UIEventAttribution { required this.purchaser}); ///Gets a possible [UIEventAttribution] instance from a [Map] value. - static UIEventAttribution? fromMap(Map? map) { + static UIEventAttribution? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -49,7 +50,7 @@ class UIEventAttribution { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "destinationURL": destinationURL.toString(), "purchaser": purchaser, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart index bafb8a76f..7d6f74674 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart @@ -1,7 +1,8 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'ui_image.g.dart'; ///Class that represents an object that manages iOS and MacOS image data in your app. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart index 67f1fb621..9f17ed58b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart @@ -32,12 +32,14 @@ class UIImage { } ///Gets a possible [UIImage] instance from a [Map] value. - static UIImage? fromMap(Map? map) { + static UIImage? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = UIImage( - data: map['data'], + data: map['data'] != null + ? Uint8List.fromList(map['data'].cast()) + : null, name: map['name'], systemName: map['systemName'], ); @@ -45,7 +47,7 @@ class UIImage { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "data": data, "name": name, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart index 3a0871bcf..fd6277469 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart @@ -82,20 +82,43 @@ class UnderlineStyle { return null; } + /// Gets a possible [UnderlineStyle] instance value with name [name]. + /// + /// Goes through [UnderlineStyle.values] looking for a value with + /// name [name], as reported by [UnderlineStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static UnderlineStyle? byName(String? name) { + if (name != null) { + try { + return UnderlineStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UnderlineStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in UnderlineStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 32768: return 'BY_WORD'; @@ -118,6 +141,17 @@ class UnderlineStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants for the underline style and strikethrough style attribute keys. @@ -198,20 +232,44 @@ class IOSNSUnderlineStyle { return null; } + /// Gets a possible [IOSNSUnderlineStyle] instance value with name [name]. + /// + /// Goes through [IOSNSUnderlineStyle.values] looking for a value with + /// name [name], as reported by [IOSNSUnderlineStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSUnderlineStyle? byName(String? name) { + if (name != null) { + try { + return IOSNSUnderlineStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSUnderlineStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSUnderlineStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 32768: return 'BY_WORD'; @@ -234,4 +292,15 @@ class IOSNSUnderlineStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart index aefb1df06..1d2f73aef 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'url_protection_space.dart'; +import 'enum_method.dart'; part 'url_authentication_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart index e62ddc4c6..b0258fea6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart @@ -14,21 +14,23 @@ class URLAuthenticationChallenge { URLAuthenticationChallenge({required this.protectionSpace}); ///Gets a possible [URLAuthenticationChallenge] instance from a [Map] value. - static URLAuthenticationChallenge? fromMap(Map? map) { + static URLAuthenticationChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLAuthenticationChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart index 0bb91451e..54eddc6dc 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart @@ -1,13 +1,14 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../x509_certificate/x509_certificate.dart'; import 'url_credential_persistence.dart'; +import 'enum_method.dart'; part 'url_credential.g.dart'; -List? _certificatesDeserializer(dynamic value) { +List? _certificatesDeserializer(dynamic value, + {EnumMethod? enumMethod}) { List? certificates; if (value != null) { certificates = []; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart index 6a135cb0a..90b481db4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart @@ -50,28 +50,48 @@ class URLCredential { } ///Gets a possible [URLCredential] instance from a [Map] value. - static URLCredential? fromMap(Map? map) { + static URLCredential? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLCredential( - certificates: _certificatesDeserializer(map['certificates']), - iosCertificates: _certificatesDeserializer(map['certificates']), - iosPersistence: + certificates: _certificatesDeserializer(map['certificates'], + enumMethod: enumMethod), + iosCertificates: _certificatesDeserializer(map['certificates'], + enumMethod: enumMethod), + iosPersistence: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSURLCredentialPersistence.fromNativeValue(map['persistence']), + EnumMethod.value => + IOSURLCredentialPersistence.fromValue(map['persistence']), + EnumMethod.name => + IOSURLCredentialPersistence.byName(map['persistence']) + }, password: map['password'], - persistence: URLCredentialPersistence.fromNativeValue(map['persistence']), + persistence: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLCredentialPersistence.fromNativeValue(map['persistence']), + EnumMethod.value => + URLCredentialPersistence.fromValue(map['persistence']), + EnumMethod.name => URLCredentialPersistence.byName(map['persistence']) + }, username: map['username'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "certificates": certificates?.map((e) => e.toMap()).toList(), + "certificates": + certificates?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "password": password, - "persistence": persistence?.toNativeValue(), + "persistence": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => persistence?.toNativeValue(), + EnumMethod.value => persistence?.toValue(), + EnumMethod.name => persistence?.name() + }, "username": username, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart index 75b4d5535..dd5a05f6a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart @@ -63,20 +63,44 @@ class URLCredentialPersistence { return null; } + /// Gets a possible [URLCredentialPersistence] instance value with name [name]. + /// + /// Goes through [URLCredentialPersistence.values] looking for a value with + /// name [name], as reported by [URLCredentialPersistence.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLCredentialPersistence? byName(String? name) { + if (name != null) { + try { + return URLCredentialPersistence.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLCredentialPersistence] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLCredentialPersistence.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'FOR_SESSION'; @@ -89,6 +113,17 @@ class URLCredentialPersistence { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class that represents the constants that specify how long the credential will be kept. @@ -150,20 +185,45 @@ class IOSURLCredentialPersistence { return null; } + /// Gets a possible [IOSURLCredentialPersistence] instance value with name [name]. + /// + /// Goes through [IOSURLCredentialPersistence.values] looking for a value with + /// name [name], as reported by [IOSURLCredentialPersistence.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLCredentialPersistence? byName(String? name) { + if (name != null) { + try { + return IOSURLCredentialPersistence.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLCredentialPersistence] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLCredentialPersistence.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'FOR_SESSION'; @@ -176,4 +236,15 @@ class IOSURLCredentialPersistence { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart index 2e18696b3..a2fd0e721 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart @@ -1,5 +1,4 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../x509_certificate/x509_certificate.dart'; @@ -7,10 +6,12 @@ import 'url_protection_space_proxy_type.dart'; import 'url_protection_space_authentication_method.dart'; import 'ssl_error.dart'; import 'ssl_certificate.dart'; +import 'enum_method.dart'; part 'url_protection_space.g.dart'; -List? _distinguishedNamesDeserializer(dynamic value) { +List? _distinguishedNamesDeserializer(dynamic value, + {EnumMethod? enumMethod}) { List? distinguishedNames; if (value != null) { distinguishedNames = []; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart index 9af49ce84..55ad5df09 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart @@ -105,50 +105,90 @@ class URLProtectionSpace { } ///Gets a possible [URLProtectionSpace] instance from a [Map] value. - static URLProtectionSpace? fromMap(Map? map) { + static URLProtectionSpace? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLProtectionSpace( - authenticationMethod: + authenticationMethod: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => URLProtectionSpaceAuthenticationMethod.fromNativeValue( map['authenticationMethod']), - distinguishedNames: - _distinguishedNamesDeserializer(map['distinguishedNames']), + EnumMethod.value => URLProtectionSpaceAuthenticationMethod.fromValue( + map['authenticationMethod']), + EnumMethod.name => URLProtectionSpaceAuthenticationMethod.byName( + map['authenticationMethod']) + }, + distinguishedNames: _distinguishedNamesDeserializer( + map['distinguishedNames'], + enumMethod: enumMethod), host: map['host'], - iosAuthenticationMethod: + iosAuthenticationMethod: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSNSURLProtectionSpaceAuthenticationMethod.fromNativeValue( map['authenticationMethod']), - iosDistinguishedNames: - _distinguishedNamesDeserializer(map['distinguishedNames']), - iosProxyType: + EnumMethod.value => + IOSNSURLProtectionSpaceAuthenticationMethod.fromValue( + map['authenticationMethod']), + EnumMethod.name => IOSNSURLProtectionSpaceAuthenticationMethod.byName( + map['authenticationMethod']) + }, + iosDistinguishedNames: _distinguishedNamesDeserializer( + map['distinguishedNames'], + enumMethod: enumMethod), + iosProxyType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSNSURLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + EnumMethod.value => + IOSNSURLProtectionSpaceProxyType.fromValue(map['proxyType']), + EnumMethod.name => + IOSNSURLProtectionSpaceProxyType.byName(map['proxyType']) + }, iosReceivesCredentialSecurely: map['receivesCredentialSecurely'], port: map['port'], protocol: map['protocol'], - proxyType: URLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + proxyType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + EnumMethod.value => + URLProtectionSpaceProxyType.fromValue(map['proxyType']), + EnumMethod.name => URLProtectionSpaceProxyType.byName(map['proxyType']) + }, realm: map['realm'], receivesCredentialSecurely: map['receivesCredentialSecurely'], sslCertificate: SslCertificate.fromMap( - map['sslCertificate']?.cast()), - sslError: SslError.fromMap(map['sslError']?.cast()), + map['sslCertificate']?.cast(), + enumMethod: enumMethod), + sslError: SslError.fromMap(map['sslError']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "authenticationMethod": authenticationMethod?.toNativeValue(), - "distinguishedNames": distinguishedNames?.map((e) => e.toMap()).toList(), + "authenticationMethod": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => authenticationMethod?.toNativeValue(), + EnumMethod.value => authenticationMethod?.toValue(), + EnumMethod.name => authenticationMethod?.name() + }, + "distinguishedNames": distinguishedNames + ?.map((e) => e.toMap(enumMethod: enumMethod)) + .toList(), "host": host, "port": port, "protocol": protocol, - "proxyType": proxyType?.toNativeValue(), + "proxyType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => proxyType?.toNativeValue(), + EnumMethod.value => proxyType?.toValue(), + EnumMethod.name => proxyType?.name() + }, "realm": realm, "receivesCredentialSecurely": receivesCredentialSecurely, - "sslCertificate": sslCertificate?.toMap(), - "sslError": sslError?.toMap(), + "sslCertificate": sslCertificate?.toMap(enumMethod: enumMethod), + "sslError": sslError?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart index a8f318ead..16e75e5ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart @@ -78,12 +78,58 @@ class URLProtectionSpaceAuthenticationMethod { return null; } + /// Gets a possible [URLProtectionSpaceAuthenticationMethod] instance value with name [name]. + /// + /// Goes through [URLProtectionSpaceAuthenticationMethod.values] looking for a value with + /// name [name], as reported by [URLProtectionSpaceAuthenticationMethod.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLProtectionSpaceAuthenticationMethod? byName(String? name) { + if (name != null) { + try { + return URLProtectionSpaceAuthenticationMethod.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLProtectionSpaceAuthenticationMethod] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLProtectionSpaceAuthenticationMethod.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLAuthenticationMethodClientCertificate': + return 'NSURL_AUTHENTICATION_METHOD_CLIENT_CERTIFICATE'; + case 'NSURLAuthenticationMethodNegotiate': + return 'NSURL_AUTHENTICATION_METHOD_NEGOTIATE'; + case 'NSURLAuthenticationMethodNTLM': + return 'NSURL_AUTHENTICATION_METHOD_NTLM'; + case 'NSURLAuthenticationMethodServerTrust': + return 'NSURL_AUTHENTICATION_METHOD_SERVER_TRUST'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -172,12 +218,58 @@ class IOSNSURLProtectionSpaceAuthenticationMethod { return null; } + /// Gets a possible [IOSNSURLProtectionSpaceAuthenticationMethod] instance value with name [name]. + /// + /// Goes through [IOSNSURLProtectionSpaceAuthenticationMethod.values] looking for a value with + /// name [name], as reported by [IOSNSURLProtectionSpaceAuthenticationMethod.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSURLProtectionSpaceAuthenticationMethod? byName(String? name) { + if (name != null) { + try { + return IOSNSURLProtectionSpaceAuthenticationMethod.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSURLProtectionSpaceAuthenticationMethod] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSURLProtectionSpaceAuthenticationMethod.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLAuthenticationMethodClientCertificate': + return 'NSURL_AUTHENTICATION_METHOD_CLIENT_CERTIFICATE'; + case 'NSURLAuthenticationMethodNegotiate': + return 'NSURL_AUTHENTICATION_METHOD_NEGOTIATE'; + case 'NSURLAuthenticationMethodNTLM': + return 'NSURL_AUTHENTICATION_METHOD_NTLM'; + case 'NSURLAuthenticationMethodServerTrust': + return 'NSURL_AUTHENTICATION_METHOD_SERVER_TRUST'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart index b5a67de50..6ca648247 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_protection_space.dart'; import 'url_credential.dart'; import '../platform_http_auth_credentials_database.dart'; +import 'enum_method.dart'; part 'url_protection_space_http_auth_credentials.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart index 744177cb3..9f1ec6d93 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart @@ -19,26 +19,30 @@ class URLProtectionSpaceHttpAuthCredentials { ///Gets a possible [URLProtectionSpaceHttpAuthCredentials] instance from a [Map] value. static URLProtectionSpaceHttpAuthCredentials? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLProtectionSpaceHttpAuthCredentials( credentials: map['credentials'] != null - ? List.from(map['credentials'] - .map((e) => URLCredential.fromMap(e?.cast())!)) + ? List.from(map['credentials'].map((e) => + URLCredential.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast()), + map['protectionSpace']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "credentials": credentials?.map((e) => e.toMap()).toList(), - "protectionSpace": protectionSpace?.toMap(), + "credentials": + credentials?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "protectionSpace": protectionSpace?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart index e174e5823..b234553c7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart @@ -70,12 +70,58 @@ class URLProtectionSpaceProxyType { return null; } + /// Gets a possible [URLProtectionSpaceProxyType] instance value with name [name]. + /// + /// Goes through [URLProtectionSpaceProxyType.values] looking for a value with + /// name [name], as reported by [URLProtectionSpaceProxyType.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLProtectionSpaceProxyType? byName(String? name) { + if (name != null) { + try { + return URLProtectionSpaceProxyType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLProtectionSpaceProxyType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLProtectionSpaceProxyType.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLProtectionSpaceFTPProxy': + return 'URL_PROTECTION_SPACE_FTP_PROXY'; + case 'NSURLProtectionSpaceHTTPSProxy': + return 'URL_PROTECTION_SPACE_HTTPS_PROXY'; + case 'NSURLProtectionSpaceHTTPProxy': + return 'URL_PROTECTION_SPACE_HTTP_PROXY'; + case 'NSURLProtectionSpaceSOCKSProxy': + return 'URL_PROTECTION_SPACE_SOCKS_PROXY'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -155,12 +201,58 @@ class IOSNSURLProtectionSpaceProxyType { return null; } + /// Gets a possible [IOSNSURLProtectionSpaceProxyType] instance value with name [name]. + /// + /// Goes through [IOSNSURLProtectionSpaceProxyType.values] looking for a value with + /// name [name], as reported by [IOSNSURLProtectionSpaceProxyType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSURLProtectionSpaceProxyType? byName(String? name) { + if (name != null) { + try { + return IOSNSURLProtectionSpaceProxyType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSURLProtectionSpaceProxyType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSURLProtectionSpaceProxyType.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLProtectionSpaceFTPProxy': + return 'NSURL_PROTECTION_SPACE_FTP_PROXY'; + case 'NSURLProtectionSpaceHTTPSProxy': + return 'NSURL_PROTECTION_SPACE_HTTPS_PROXY'; + case 'NSURLProtectionSpaceSOCKSProxy': + return 'NSURL_PROTECTION_SPACE_SOCKS_PROXY'; + case 'NSURLProtectionSpaceHTTPProxy': + return 'NSUR_PROTECTION_SPACE_HTTP_PROXY'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart index e737a6f7e..ed66b6dc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart @@ -5,6 +5,7 @@ import '../web_uri.dart'; import 'url_request_cache_policy.dart'; import 'url_request_network_service_type.dart'; import 'url_request_attribution.dart'; +import 'enum_method.dart'; part 'url_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart index 4f6bdc3c0..9c99ea0c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart @@ -189,7 +189,8 @@ class URLRequest { } ///Gets a possible [URLRequest] instance from a [Map] value. - static URLRequest? fromMap(Map? map) { + static URLRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -198,31 +199,61 @@ class URLRequest { allowsConstrainedNetworkAccess: map['allowsConstrainedNetworkAccess'], allowsExpensiveNetworkAccess: map['allowsExpensiveNetworkAccess'], assumesHTTP3Capable: map['assumesHTTP3Capable'], - attribution: URLRequestAttribution.fromNativeValue(map['attribution']), - body: map['body'], - cachePolicy: URLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + attribution: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLRequestAttribution.fromNativeValue(map['attribution']), + EnumMethod.value => URLRequestAttribution.fromValue(map['attribution']), + EnumMethod.name => URLRequestAttribution.byName(map['attribution']) + }, + body: map['body'] != null + ? Uint8List.fromList(map['body'].cast()) + : null, + cachePolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + EnumMethod.value => URLRequestCachePolicy.fromValue(map['cachePolicy']), + EnumMethod.name => URLRequestCachePolicy.byName(map['cachePolicy']) + }, headers: map['headers']?.cast(), httpShouldHandleCookies: map['httpShouldHandleCookies'], httpShouldUsePipelining: map['httpShouldUsePipelining'], iosAllowsCellularAccess: map['allowsCellularAccess'], iosAllowsConstrainedNetworkAccess: map['allowsConstrainedNetworkAccess'], iosAllowsExpensiveNetworkAccess: map['allowsExpensiveNetworkAccess'], - iosCachePolicy: + iosCachePolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSURLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + EnumMethod.value => + IOSURLRequestCachePolicy.fromValue(map['cachePolicy']), + EnumMethod.name => IOSURLRequestCachePolicy.byName(map['cachePolicy']) + }, iosHttpShouldHandleCookies: map['httpShouldHandleCookies'], iosHttpShouldUsePipelining: map['httpShouldUsePipelining'], iosMainDocumentURL: map['mainDocumentURL'] != null ? Uri.tryParse(map['mainDocumentURL']) : null, - iosNetworkServiceType: IOSURLRequestNetworkServiceType.fromNativeValue( - map['networkServiceType']), + iosNetworkServiceType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + IOSURLRequestNetworkServiceType.fromNativeValue( + map['networkServiceType']), + EnumMethod.value => + IOSURLRequestNetworkServiceType.fromValue(map['networkServiceType']), + EnumMethod.name => + IOSURLRequestNetworkServiceType.byName(map['networkServiceType']) + }, iosTimeoutInterval: map['timeoutInterval'], mainDocumentURL: map['mainDocumentURL'] != null ? WebUri(map['mainDocumentURL']) : null, method: map['method'], - networkServiceType: URLRequestNetworkServiceType.fromNativeValue( - map['networkServiceType']), + networkServiceType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => URLRequestNetworkServiceType.fromNativeValue( + map['networkServiceType']), + EnumMethod.value => + URLRequestNetworkServiceType.fromValue(map['networkServiceType']), + EnumMethod.name => + URLRequestNetworkServiceType.byName(map['networkServiceType']) + }, timeoutInterval: map['timeoutInterval'], url: map['url'] != null ? WebUri(map['url']) : null, ); @@ -230,21 +261,33 @@ class URLRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsCellularAccess": allowsCellularAccess, "allowsConstrainedNetworkAccess": allowsConstrainedNetworkAccess, "allowsExpensiveNetworkAccess": allowsExpensiveNetworkAccess, "assumesHTTP3Capable": assumesHTTP3Capable, - "attribution": attribution?.toNativeValue(), + "attribution": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => attribution?.toNativeValue(), + EnumMethod.value => attribution?.toValue(), + EnumMethod.name => attribution?.name() + }, "body": body, - "cachePolicy": cachePolicy?.toNativeValue(), + "cachePolicy": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => cachePolicy?.toNativeValue(), + EnumMethod.value => cachePolicy?.toValue(), + EnumMethod.name => cachePolicy?.name() + }, "headers": headers, "httpShouldHandleCookies": httpShouldHandleCookies, "httpShouldUsePipelining": httpShouldUsePipelining, "mainDocumentURL": mainDocumentURL?.toString(), "method": method, - "networkServiceType": networkServiceType?.toNativeValue(), + "networkServiceType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => networkServiceType?.toNativeValue(), + EnumMethod.value => networkServiceType?.toValue(), + EnumMethod.name => networkServiceType?.name() + }, "timeoutInterval": timeoutInterval, "url": url?.toString(), }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart index e40c4545f..82480202f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart @@ -60,12 +60,53 @@ class URLRequestAttribution { return null; } + /// Gets a possible [URLRequestAttribution] instance value with name [name]. + /// + /// Goes through [URLRequestAttribution.values] looking for a value with + /// name [name], as reported by [URLRequestAttribution.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestAttribution? byName(String? name) { + if (name != null) { + try { + return URLRequestAttribution.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestAttribution] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestAttribution.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'DEVELOPER'; + case 1: + return 'USER'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -74,12 +115,6 @@ class URLRequestAttribution { @override String toString() { - switch (_value) { - case 0: - return 'DEVELOPER'; - case 1: - return 'USER'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart index 8424682f8..5bfeeba2a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart @@ -88,20 +88,44 @@ class URLRequestCachePolicy { return null; } + /// Gets a possible [URLRequestCachePolicy] instance value with name [name]. + /// + /// Goes through [URLRequestCachePolicy.values] looking for a value with + /// name [name], as reported by [URLRequestCachePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestCachePolicy? byName(String? name) { + if (name != null) { + try { + return URLRequestCachePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestCachePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestCachePolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'RELOAD_IGNORING_LOCAL_AND_REMOTE_CACHE_DATA'; @@ -118,6 +142,17 @@ class URLRequestCachePolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants used to specify interaction with the cached responses. @@ -204,20 +239,44 @@ class IOSURLRequestCachePolicy { return null; } + /// Gets a possible [IOSURLRequestCachePolicy] instance value with name [name]. + /// + /// Goes through [IOSURLRequestCachePolicy.values] looking for a value with + /// name [name], as reported by [IOSURLRequestCachePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLRequestCachePolicy? byName(String? name) { + if (name != null) { + try { + return IOSURLRequestCachePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLRequestCachePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLRequestCachePolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'RELOAD_IGNORING_LOCAL_AND_REMOTE_CACHE_DATA'; @@ -234,4 +293,15 @@ class IOSURLRequestCachePolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart index 7e9d6d3dc..2c0ee1e49 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart @@ -85,20 +85,45 @@ class URLRequestNetworkServiceType { return null; } + /// Gets a possible [URLRequestNetworkServiceType] instance value with name [name]. + /// + /// Goes through [URLRequestNetworkServiceType.values] looking for a value with + /// name [name], as reported by [URLRequestNetworkServiceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestNetworkServiceType? byName(String? name) { + if (name != null) { + try { + return URLRequestNetworkServiceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestNetworkServiceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestNetworkServiceType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 8: return 'AV_STREAMING'; @@ -119,6 +144,17 @@ class URLRequestNetworkServiceType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants that specify how a request uses network resources. @@ -205,20 +241,45 @@ class IOSURLRequestNetworkServiceType { return null; } + /// Gets a possible [IOSURLRequestNetworkServiceType] instance value with name [name]. + /// + /// Goes through [IOSURLRequestNetworkServiceType.values] looking for a value with + /// name [name], as reported by [IOSURLRequestNetworkServiceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLRequestNetworkServiceType? byName(String? name) { + if (name != null) { + try { + return IOSURLRequestNetworkServiceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLRequestNetworkServiceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLRequestNetworkServiceType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 8: return 'AV_STREAMING'; @@ -239,4 +300,15 @@ class IOSURLRequestNetworkServiceType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart index 5896d0e93..72316d7bb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'url_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart index afce902ab..5a59dba61 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart @@ -38,7 +38,8 @@ class URLResponse { this.url}); ///Gets a possible [URLResponse] instance from a [Map] value. - static URLResponse? fromMap(Map? map) { + static URLResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -55,7 +56,7 @@ class URLResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "expectedContentLength": expectedContentLength, "headers": headers, @@ -111,7 +112,8 @@ class IOSURLResponse { this.url}); ///Gets a possible [IOSURLResponse] instance from a [Map] value. - static IOSURLResponse? fromMap(Map? map) { + static IOSURLResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -128,7 +130,7 @@ class IOSURLResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "expectedContentLength": expectedContentLength, "headers": headers, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart index 38f1ec5ba..eadb1d2f4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart @@ -58,20 +58,44 @@ class UserPreferredContentMode { return null; } + /// Gets a possible [UserPreferredContentMode] instance value with name [name]. + /// + /// Goes through [UserPreferredContentMode.values] looking for a value with + /// name [name], as reported by [UserPreferredContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static UserPreferredContentMode? byName(String? name) { + if (name != null) { + try { + return UserPreferredContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UserPreferredContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in UserPreferredContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 2: return 'DESKTOP'; @@ -82,4 +106,15 @@ class UserPreferredContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart index cae6b382e..7320e6a15 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart @@ -1,8 +1,9 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'user_script_injection_time.dart'; import 'content_world.dart'; -import '../platform_webview_feature.dart'; +import '../in_app_webview/platform_inappwebview_controller.dart'; part 'user_script.g.dart'; @@ -23,19 +24,38 @@ class UserScript_ { bool? iosForMainFrameOnly; ///A Boolean value that indicates whether to inject the script into the main frame. - ///Specify true to inject the script only into the main frame, or false to inject it into all frames. + ///Specify `true` to inject the script only into the main frame, or false to inject it into all frames. ///The default value is `true`. - /// - ///**NOTE**: available only on iOS and MacOS. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) bool forMainFrameOnly; ///A set of matching rules for the allowed origins. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. /// - ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) late Set allowedOriginRules; ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. + /// + ///**NOTE for Android**: because of how a Content World is implemented on Android, if [forMainFrameOnly] is `true`, + ///the [source] inside a specific Content World that is not [ContentWorld.PAGE] will not be executed. + ///See [ContentWorld] for more details. late ContentWorld contentWorld; @ExchangeableObjectConstructor() diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart index 99f114be0..9eb75b082 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart @@ -9,19 +9,38 @@ part of 'user_script.dart'; ///Class that represents a script that the `WebView` injects into the web page. class UserScript { ///A set of matching rules for the allowed origins. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. /// - ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows late Set allowedOriginRules; ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. + /// + ///**NOTE for Android**: because of how a Content World is implemented on Android, if [forMainFrameOnly] is `true`, + ///the [source] inside a specific Content World that is not [ContentWorld.PAGE] will not be executed. + ///See [ContentWorld] for more details. late ContentWorld contentWorld; ///A Boolean value that indicates whether to inject the script into the main frame. - ///Specify true to inject the script only into the main frame, or false to inject it into all frames. + ///Specify `true` to inject the script only into the main frame, or false to inject it into all frames. ///The default value is `true`. /// - ///**NOTE**: available only on iOS and MacOS. + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows bool forMainFrameOnly; ///The script’s group name. @@ -53,32 +72,50 @@ class UserScript { } ///Gets a possible [UserScript] instance from a [Map] value. - static UserScript? fromMap(Map? map) { + static UserScript? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = UserScript( groupName: map['groupName'], - injectionTime: - UserScriptInjectionTime.fromNativeValue(map['injectionTime'])!, + injectionTime: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + UserScriptInjectionTime.fromNativeValue(map['injectionTime']), + EnumMethod.value => + UserScriptInjectionTime.fromValue(map['injectionTime']), + EnumMethod.name => UserScriptInjectionTime.byName(map['injectionTime']) + }!, iosForMainFrameOnly: map['forMainFrameOnly'], source: map['source'], ); - instance.allowedOriginRules = - Set.from(map['allowedOriginRules']!.cast()); - instance.contentWorld = map['contentWorld']; - instance.forMainFrameOnly = map['forMainFrameOnly']; + if (map['allowedOriginRules'] != null) { + instance.allowedOriginRules = + Set.from(map['allowedOriginRules']!.cast()); + } + if (map['contentWorld'] != null) { + instance.contentWorld = ContentWorld.fromMap( + map['contentWorld']?.cast(), + enumMethod: enumMethod)!; + } + if (map['forMainFrameOnly'] != null) { + instance.forMainFrameOnly = map['forMainFrameOnly']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowedOriginRules": allowedOriginRules.toList(), - "contentWorld": contentWorld.toMap(), + "contentWorld": contentWorld.toMap(enumMethod: enumMethod), "forMainFrameOnly": forMainFrameOnly, "groupName": groupName, - "injectionTime": injectionTime.toNativeValue(), + "injectionTime": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => injectionTime.toNativeValue(), + EnumMethod.value => injectionTime.toValue(), + EnumMethod.name => injectionTime.name() + }, "source": source, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart index 1dd328d73..d7b270043 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart @@ -58,12 +58,53 @@ class UserScriptInjectionTime { return null; } + /// Gets a possible [UserScriptInjectionTime] instance value with name [name]. + /// + /// Goes through [UserScriptInjectionTime.values] looking for a value with + /// name [name], as reported by [UserScriptInjectionTime.name]. + /// Returns the first value with the given name, otherwise `null`. + static UserScriptInjectionTime? byName(String? name) { + if (name != null) { + try { + return UserScriptInjectionTime.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UserScriptInjectionTime] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in UserScriptInjectionTime.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'AT_DOCUMENT_END'; + case 0: + return 'AT_DOCUMENT_START'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -72,12 +113,6 @@ class UserScriptInjectionTime { @override String toString() { - switch (_value) { - case 1: - return 'AT_DOCUMENT_END'; - case 0: - return 'AT_DOCUMENT_START'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart index c0c9ff89f..655090c24 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart @@ -61,20 +61,45 @@ class VerticalScrollbarPosition { return null; } + /// Gets a possible [VerticalScrollbarPosition] instance value with name [name]. + /// + /// Goes through [VerticalScrollbarPosition.values] looking for a value with + /// name [name], as reported by [VerticalScrollbarPosition.name]. + /// Returns the first value with the given name, otherwise `null`. + static VerticalScrollbarPosition? byName(String? name) { + if (name != null) { + try { + return VerticalScrollbarPosition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [VerticalScrollbarPosition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in VerticalScrollbarPosition.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'SCROLLBAR_POSITION_DEFAULT'; @@ -85,6 +110,17 @@ class VerticalScrollbarPosition { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the position of the vertical scroll bar. @@ -145,20 +181,45 @@ class AndroidVerticalScrollbarPosition { return null; } + /// Gets a possible [AndroidVerticalScrollbarPosition] instance value with name [name]. + /// + /// Goes through [AndroidVerticalScrollbarPosition.values] looking for a value with + /// name [name], as reported by [AndroidVerticalScrollbarPosition.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidVerticalScrollbarPosition? byName(String? name) { + if (name != null) { + try { + return AndroidVerticalScrollbarPosition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidVerticalScrollbarPosition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidVerticalScrollbarPosition.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'SCROLLBAR_POSITION_DEFAULT'; @@ -169,4 +230,15 @@ class AndroidVerticalScrollbarPosition { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart index 661b12ecb..28d47d072 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart @@ -55,12 +55,53 @@ class WebArchiveFormat { return null; } + /// Gets a possible [WebArchiveFormat] instance value with name [name]. + /// + /// Goes through [WebArchiveFormat.values] looking for a value with + /// name [name], as reported by [WebArchiveFormat.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebArchiveFormat? byName(String? name) { + if (name != null) { + try { + return WebArchiveFormat.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebArchiveFormat] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebArchiveFormat.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'mht': + return 'MHT'; + case 'webarchive': + return 'WEBARCHIVE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart index a26174420..268c9f0a0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart @@ -60,20 +60,45 @@ class WebAuthenticationSessionError { return null; } + /// Gets a possible [WebAuthenticationSessionError] instance value with name [name]. + /// + /// Goes through [WebAuthenticationSessionError.values] looking for a value with + /// name [name], as reported by [WebAuthenticationSessionError.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebAuthenticationSessionError? byName(String? name) { + if (name != null) { + try { + return WebAuthenticationSessionError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebAuthenticationSessionError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebAuthenticationSessionError.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'CANCELED_LOGIN'; @@ -84,4 +109,15 @@ class WebAuthenticationSessionError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart index 437ab0205..aefe74dc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'web_history_item.dart'; +import 'enum_method.dart'; part 'web_history.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart index 8fc9c5f28..3feae8759 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart @@ -16,25 +16,27 @@ class WebHistory { WebHistory({this.currentIndex, this.list}); ///Gets a possible [WebHistory] instance from a [Map] value. - static WebHistory? fromMap(Map? map) { + static WebHistory? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebHistory( currentIndex: map['currentIndex'], list: map['list'] != null - ? List.from(map['list'] - .map((e) => WebHistoryItem.fromMap(e?.cast())!)) + ? List.from(map['list'].map((e) => + WebHistoryItem.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "currentIndex": currentIndex, - "list": list?.map((e) => e.toMap()).toList(), + "list": list?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart index 84fc615f9..c09e9cb48 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../web_uri.dart'; import 'web_history.dart'; +import 'enum_method.dart'; part 'web_history_item.g.dart'; @@ -24,6 +25,15 @@ class WebHistoryItem_ { ///Position offset respect to the currentIndex of the back-forward [WebHistory.list]. int? offset; + ///Unique id of the navigation history entry. + @SupportedPlatforms(platforms: [WindowsPlatform()]) + int? entryId; + WebHistoryItem_( - {this.originalUrl, this.title, this.url, this.index, this.offset}); + {this.originalUrl, + this.title, + this.url, + this.index, + this.offset, + this.entryId}); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart index 790e503d8..698c0503f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart @@ -9,6 +9,12 @@ part of 'web_history_item.dart'; ///A convenience class for accessing fields in an entry in the back/forward list of a `WebView`. ///Each [WebHistoryItem] is a snapshot of the requested history item. class WebHistoryItem { + ///Unique id of the navigation history entry. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? entryId; + ///0-based position index in the back-forward [WebHistory.list]. int? index; @@ -24,14 +30,21 @@ class WebHistoryItem { ///Url of this history item. WebUri? url; WebHistoryItem( - {this.index, this.offset, this.originalUrl, this.title, this.url}); + {this.entryId, + this.index, + this.offset, + this.originalUrl, + this.title, + this.url}); ///Gets a possible [WebHistoryItem] instance from a [Map] value. - static WebHistoryItem? fromMap(Map? map) { + static WebHistoryItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebHistoryItem( + entryId: map['entryId'], index: map['index'], offset: map['offset'], originalUrl: @@ -43,8 +56,9 @@ class WebHistoryItem { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { + "entryId": entryId, "index": index, "offset": offset, "originalUrl": originalUrl?.toString(), @@ -60,6 +74,6 @@ class WebHistoryItem { @override String toString() { - return 'WebHistoryItem{index: $index, offset: $offset, originalUrl: $originalUrl, title: $title, url: $url}'; + return 'WebHistoryItem{entryId: $entryId, index: $index, offset: $offset, originalUrl: $originalUrl, title: $title, url: $url}'; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart index de20e8c11..a10b0e1eb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'web_resource_error_type.dart'; +import 'enum_method.dart'; part 'web_resource_error.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart index 9638cf607..184c1327e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart @@ -16,22 +16,32 @@ class WebResourceError { WebResourceError({required this.description, required this.type}); ///Gets a possible [WebResourceError] instance from a [Map] value. - static WebResourceError? fromMap(Map? map) { + static WebResourceError? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebResourceError( description: map['description'], - type: WebResourceErrorType.fromNativeValue(map['type'])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WebResourceErrorType.fromNativeValue(map['type']), + EnumMethod.value => WebResourceErrorType.fromValue(map['type']), + EnumMethod.name => WebResourceErrorType.byName(map['type']) + }!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "description": description, - "type": type.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart index 822de4cbf..ef9a27b44 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart @@ -60,7 +60,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cannotConnectToHost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost', - value: -1004) + value: -1004), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CANNOT_CONNECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 12) ]) static const CANNOT_CONNECT_TO_HOST = WebResourceErrorType_._internal("CANNOT_CONNECT_TO_HOST"); @@ -124,7 +129,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cannotFindHost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost', - value: -1003) + value: -1003), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_HOST_NAME_NOT_RESOLVED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 13) ]) static const HOST_LOOKUP = WebResourceErrorType_._internal("HOST_LOOKUP"); @@ -186,7 +196,12 @@ class WebResourceErrorType_ { apiName: 'URLError.timedOut', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout', - value: -1001) + value: -1001), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_TIMEOUT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 7) ]) static const TIMEOUT = WebResourceErrorType_._internal("TIMEOUT"); @@ -217,7 +232,12 @@ class WebResourceErrorType_ { apiName: 'URLError.unknown', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown', - value: -1) + value: -1), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 0) ]) static const UNKNOWN = WebResourceErrorType_._internal("UNKNOWN"); @@ -276,7 +296,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cancelled', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled', - value: -999) + value: -999), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 14) ]) static const CANCELLED = WebResourceErrorType_._internal("CANCELLED"); @@ -291,7 +316,12 @@ class WebResourceErrorType_ { apiName: 'URLError.networkConnectionLost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost', - value: -1005) + value: -1005), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 11) ]) static const NETWORK_CONNECTION_LOST = WebResourceErrorType_._internal("NETWORK_CONNECTION_LOST"); @@ -356,7 +386,13 @@ class WebResourceErrorType_ { apiName: 'URLError.badServerResponse', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse', - value: -1011) + value: -1011), + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_ERROR_HTTP_INVALID_SERVER_RESPONSE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 8) ]) static const BAD_SERVER_RESPONSE = WebResourceErrorType_._internal("BAD_SERVER_RESPONSE"); @@ -389,7 +425,13 @@ class WebResourceErrorType_ { apiName: 'URLError.userAuthenticationRequired', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired', - value: -1013) + value: -1013), + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_VALID_AUTHENTICATION_CREDENTIALS_REQUIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 17), ]) static const USER_AUTHENTICATION_REQUIRED = WebResourceErrorType_._internal("USER_AUTHENTICATION_REQUIRED"); @@ -892,4 +934,70 @@ class WebResourceErrorType_ { ]) static const BACKGROUND_SESSION_WAS_DISCONNECTED = WebResourceErrorType_._internal("BACKGROUND_SESSION_WAS_DISCONNECTED"); + + ///Indicates that the host is unreachable. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_SERVER_UNREACHABLE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 6), + ]) + static const SERVER_UNREACHABLE = + WebResourceErrorType_._internal("SERVER_UNREACHABLE"); + + ///Indicates that the connection was stopped. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_ABORTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 9) + ]) + static const CONNECTION_ABORTED = + WebResourceErrorType_._internal("CONNECTION_ABORTED"); + + ///Indicates that the connection was reset. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_RESET', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 10), + ]) + static const RESET = WebResourceErrorType_._internal("RESET"); + + ///Indicates that the request redirect failed. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_REDIRECT_FAILED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 15), + ]) + static const REDIRECT_FAILED = + WebResourceErrorType_._internal("REDIRECT_FAILED"); + + ///Indicates that an unexpected error occurred. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_UNEXPECTED_ERROR', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 16), + ]) + static const UNEXPECTED_ERROR = + WebResourceErrorType_._internal("UNEXPECTED_ERROR"); + + ///Indicates that user lacks proper authentication credentials for a proxy server. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_VALID_PROXY_AUTHENTICATION_REQUIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 18), + ]) + static const VALID_PROXY_AUTHENTICATION_REQUIRED = + WebResourceErrorType_._internal("VALID_PROXY_AUTHENTICATION_REQUIRED"); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart index 4a4f905d8..f21682027 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart @@ -97,6 +97,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.badServerResponse](https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse)) ///- MacOS ([Official API - URLError.badServerResponse](https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_ERROR_HTTP_INVALID_SERVER_RESPONSE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final BAD_SERVER_RESPONSE = WebResourceErrorType._internalMultiPlatform('BAD_SERVER_RESPONSE', () { switch (defaultTargetPlatform) { @@ -104,6 +105,8 @@ class WebResourceErrorType { return -1011; case TargetPlatform.macOS: return -1011; + case TargetPlatform.windows: + return 8; default: break; } @@ -154,6 +157,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.cancelled](https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled)) ///- MacOS ([Official API - URLError.cancelled](https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final CANCELLED = WebResourceErrorType._internalMultiPlatform('CANCELLED', () { switch (defaultTargetPlatform) { @@ -161,6 +165,8 @@ class WebResourceErrorType { return -999; case TargetPlatform.macOS: return -999; + case TargetPlatform.windows: + return 14; default: break; } @@ -191,6 +197,7 @@ class WebResourceErrorType { ///- Android native WebView ([Official API - WebViewClient.ERROR_CONNECT](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_CONNECT)) ///- iOS ([Official API - URLError.cannotConnectToHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost)) ///- MacOS ([Official API - URLError.cannotConnectToHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CANNOT_CONNECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final CANNOT_CONNECT_TO_HOST = WebResourceErrorType._internalMultiPlatform('CANNOT_CONNECT_TO_HOST', () { switch (defaultTargetPlatform) { @@ -200,6 +207,8 @@ class WebResourceErrorType { return -1004; case TargetPlatform.macOS: return -1004; + case TargetPlatform.windows: + return 12; default: break; } @@ -408,6 +417,21 @@ class WebResourceErrorType { return null; }); + ///Indicates that the connection was stopped. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_ABORTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final CONNECTION_ABORTED = + WebResourceErrorType._internalMultiPlatform('CONNECTION_ABORTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 9; + default: + break; + } + return null; + }); + ///The length of the resource data exceeds the maximum allowed. /// ///**Officially Supported Platforms/Implementations**: @@ -558,6 +582,7 @@ class WebResourceErrorType { ///- Android native WebView ([Official API - WebViewClient.ERROR_HOST_LOOKUP](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_HOST_LOOKUP)) ///- iOS ([Official API - URLError.cannotFindHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost)) ///- MacOS ([Official API - URLError.cannotFindHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_HOST_NAME_NOT_RESOLVED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final HOST_LOOKUP = WebResourceErrorType._internalMultiPlatform('HOST_LOOKUP', () { switch (defaultTargetPlatform) { @@ -567,6 +592,8 @@ class WebResourceErrorType { return -1003; case TargetPlatform.macOS: return -1003; + case TargetPlatform.windows: + return 13; default: break; } @@ -611,6 +638,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.networkConnectionLost](https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost)) ///- MacOS ([Official API - URLError.networkConnectionLost](https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final NETWORK_CONNECTION_LOST = WebResourceErrorType._internalMultiPlatform('NETWORK_CONNECTION_LOST', () { @@ -619,6 +647,8 @@ class WebResourceErrorType { return -1005; case TargetPlatform.macOS: return -1005; + case TargetPlatform.windows: + return 11; default: break; } @@ -678,6 +708,21 @@ class WebResourceErrorType { return null; }); + ///Indicates that the request redirect failed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_REDIRECT_FAILED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final REDIRECT_FAILED = + WebResourceErrorType._internalMultiPlatform('REDIRECT_FAILED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 15; + default: + break; + } + return null; + }); + ///A redirect was specified by way of server response code, but the server didn’t accompany this code with a redirect URL. /// ///**Officially Supported Platforms/Implementations**: @@ -716,6 +761,20 @@ class WebResourceErrorType { return null; }); + ///Indicates that the connection was reset. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_RESET](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final RESET = WebResourceErrorType._internalMultiPlatform('RESET', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 10; + default: + break; + } + return null; + }); + ///A requested resource couldn't be retrieved. ///This error can indicate a file-not-found situation, or decoding problems that prevent data from being processed correctly. /// @@ -830,12 +889,28 @@ class WebResourceErrorType { return null; }); + ///Indicates that the host is unreachable. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_SERVER_UNREACHABLE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final SERVER_UNREACHABLE = + WebResourceErrorType._internalMultiPlatform('SERVER_UNREACHABLE', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 6; + default: + break; + } + return null; + }); + ///Connection timed out. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.ERROR_TIMEOUT](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TIMEOUT)) ///- iOS ([Official API - URLError.timedOut](https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout)) ///- MacOS ([Official API - URLError.timedOut](https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_TIMEOUT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final TIMEOUT = WebResourceErrorType._internalMultiPlatform('TIMEOUT', () { switch (defaultTargetPlatform) { @@ -845,6 +920,8 @@ class WebResourceErrorType { return -1001; case TargetPlatform.macOS: return -1001; + case TargetPlatform.windows: + return 7; default: break; } @@ -887,12 +964,28 @@ class WebResourceErrorType { return null; }); + ///Indicates that an unexpected error occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_UNEXPECTED_ERROR](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final UNEXPECTED_ERROR = + WebResourceErrorType._internalMultiPlatform('UNEXPECTED_ERROR', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 16; + default: + break; + } + return null; + }); + ///The URL Loading System encountered an error that it can’t interpret. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.ERROR_UNKNOWN](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNKNOWN)) ///- iOS ([Official API - URLError.unknown](https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown)) ///- MacOS ([Official API - URLError.unknown](https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final UNKNOWN = WebResourceErrorType._internalMultiPlatform('UNKNOWN', () { switch (defaultTargetPlatform) { @@ -902,6 +995,8 @@ class WebResourceErrorType { return -1; case TargetPlatform.macOS: return -1; + case TargetPlatform.windows: + return 0; default: break; } @@ -982,6 +1077,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.userAuthenticationRequired](https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired)) ///- MacOS ([Official API - URLError.userAuthenticationRequired](https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_VALID_AUTHENTICATION_CREDENTIALS_REQUIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final USER_AUTHENTICATION_REQUIRED = WebResourceErrorType._internalMultiPlatform( 'USER_AUTHENTICATION_REQUIRED', () { @@ -990,6 +1086,8 @@ class WebResourceErrorType { return -1013; case TargetPlatform.macOS: return -1013; + case TargetPlatform.windows: + return 17; default: break; } @@ -1016,6 +1114,22 @@ class WebResourceErrorType { return null; }); + ///Indicates that user lacks proper authentication credentials for a proxy server. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_VALID_PROXY_AUTHENTICATION_REQUIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final VALID_PROXY_AUTHENTICATION_REQUIRED = + WebResourceErrorType._internalMultiPlatform( + 'VALID_PROXY_AUTHENTICATION_REQUIRED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 18; + default: + break; + } + return null; + }); + ///A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data. /// ///**Officially Supported Platforms/Implementations**: @@ -1057,6 +1171,7 @@ class WebResourceErrorType { WebResourceErrorType.CANNOT_WRITE_TO_FILE, WebResourceErrorType.CLIENT_CERTIFICATE_REJECTED, WebResourceErrorType.CLIENT_CERTIFICATE_REQUIRED, + WebResourceErrorType.CONNECTION_ABORTED, WebResourceErrorType.DATA_LENGTH_EXCEEDS_MAXIMUM, WebResourceErrorType.DATA_NOT_ALLOWED, WebResourceErrorType.DOWNLOAD_DECODING_FAILED_MID_STREAM, @@ -1072,17 +1187,21 @@ class WebResourceErrorType { WebResourceErrorType.NOT_CONNECTED_TO_INTERNET, WebResourceErrorType.NO_PERMISSIONS_TO_READ_FILE, WebResourceErrorType.PROXY_AUTHENTICATION, + WebResourceErrorType.REDIRECT_FAILED, WebResourceErrorType.REDIRECT_TO_NON_EXISTENT_LOCATION, WebResourceErrorType.REQUEST_BODY_STREAM_EXHAUSTED, + WebResourceErrorType.RESET, WebResourceErrorType.RESOURCE_UNAVAILABLE, WebResourceErrorType.SECURE_CONNECTION_FAILED, WebResourceErrorType.SERVER_CERTIFICATE_HAS_BAD_DATE, WebResourceErrorType.SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT, WebResourceErrorType.SERVER_CERTIFICATE_NOT_YET_VALID, WebResourceErrorType.SERVER_CERTIFICATE_UNTRUSTED, + WebResourceErrorType.SERVER_UNREACHABLE, WebResourceErrorType.TIMEOUT, WebResourceErrorType.TOO_MANY_REDIRECTS, WebResourceErrorType.TOO_MANY_REQUESTS, + WebResourceErrorType.UNEXPECTED_ERROR, WebResourceErrorType.UNKNOWN, WebResourceErrorType.UNSAFE_RESOURCE, WebResourceErrorType.UNSUPPORTED_AUTH_SCHEME, @@ -1090,6 +1209,7 @@ class WebResourceErrorType { WebResourceErrorType.USER_AUTHENTICATION_FAILED, WebResourceErrorType.USER_AUTHENTICATION_REQUIRED, WebResourceErrorType.USER_CANCELLED_AUTHENTICATION, + WebResourceErrorType.VALID_PROXY_AUTHENTICATION_REQUIRED, WebResourceErrorType.ZERO_BYTE_RESOURCE, ].toSet(); @@ -1119,12 +1239,171 @@ class WebResourceErrorType { return null; } + /// Gets a possible [WebResourceErrorType] instance value with name [name]. + /// + /// Goes through [WebResourceErrorType.values] looking for a value with + /// name [name], as reported by [WebResourceErrorType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebResourceErrorType? byName(String? name) { + if (name != null) { + try { + return WebResourceErrorType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebResourceErrorType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebResourceErrorType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'APP_TRANSPORT_SECURITY_REQUIRES_SECURE_CONNECTION': + return 'APP_TRANSPORT_SECURITY_REQUIRES_SECURE_CONNECTION'; + case 'BACKGROUND_SESSION_IN_USE_BY_ANOTHER_PROCESS': + return 'BACKGROUND_SESSION_IN_USE_BY_ANOTHER_PROCESS'; + case 'BACKGROUND_SESSION_REQUIRES_SHARED_CONTAINER': + return 'BACKGROUND_SESSION_REQUIRES_SHARED_CONTAINER'; + case 'BACKGROUND_SESSION_WAS_DISCONNECTED': + return 'BACKGROUND_SESSION_WAS_DISCONNECTED'; + case 'BAD_SERVER_RESPONSE': + return 'BAD_SERVER_RESPONSE'; + case 'BAD_URL': + return 'BAD_URL'; + case 'CALL_IS_ACTIVE': + return 'CALL_IS_ACTIVE'; + case 'CANCELLED': + return 'CANCELLED'; + case 'CANNOT_CLOSE_FILE': + return 'CANNOT_CLOSE_FILE'; + case 'CANNOT_CONNECT_TO_HOST': + return 'CANNOT_CONNECT_TO_HOST'; + case 'CANNOT_CREATE_FILE': + return 'CANNOT_CREATE_FILE'; + case 'CANNOT_DECODE_CONTENT_DATA': + return 'CANNOT_DECODE_CONTENT_DATA'; + case 'CANNOT_DECODE_RAW_DATA': + return 'CANNOT_DECODE_RAW_DATA'; + case 'CANNOT_LOAD_FROM_NETWORK': + return 'CANNOT_LOAD_FROM_NETWORK'; + case 'CANNOT_MOVE_FILE': + return 'CANNOT_MOVE_FILE'; + case 'CANNOT_OPEN_FILE': + return 'CANNOT_OPEN_FILE'; + case 'CANNOT_PARSE_RESPONSE': + return 'CANNOT_PARSE_RESPONSE'; + case 'CANNOT_REMOVE_FILE': + return 'CANNOT_REMOVE_FILE'; + case 'CANNOT_WRITE_TO_FILE': + return 'CANNOT_WRITE_TO_FILE'; + case 'CLIENT_CERTIFICATE_REJECTED': + return 'CLIENT_CERTIFICATE_REJECTED'; + case 'CLIENT_CERTIFICATE_REQUIRED': + return 'CLIENT_CERTIFICATE_REQUIRED'; + case 'CONNECTION_ABORTED': + return 'CONNECTION_ABORTED'; + case 'DATA_LENGTH_EXCEEDS_MAXIMUM': + return 'DATA_LENGTH_EXCEEDS_MAXIMUM'; + case 'DATA_NOT_ALLOWED': + return 'DATA_NOT_ALLOWED'; + case 'DOWNLOAD_DECODING_FAILED_MID_STREAM': + return 'DOWNLOAD_DECODING_FAILED_MID_STREAM'; + case 'DOWNLOAD_DECODING_FAILED_TO_COMPLETE': + return 'DOWNLOAD_DECODING_FAILED_TO_COMPLETE'; + case 'FAILED_SSL_HANDSHAKE': + return 'FAILED_SSL_HANDSHAKE'; + case 'FILE_IS_DIRECTORY': + return 'FILE_IS_DIRECTORY'; + case 'FILE_NOT_FOUND': + return 'FILE_NOT_FOUND'; + case 'GENERIC_FILE_ERROR': + return 'GENERIC_FILE_ERROR'; + case 'HOST_LOOKUP': + return 'HOST_LOOKUP'; + case 'INTERNATIONAL_ROAMING_OFF': + return 'INTERNATIONAL_ROAMING_OFF'; + case 'IO': + return 'IO'; + case 'NETWORK_CONNECTION_LOST': + return 'NETWORK_CONNECTION_LOST'; + case 'NOT_CONNECTED_TO_INTERNET': + return 'NOT_CONNECTED_TO_INTERNET'; + case 'NO_PERMISSIONS_TO_READ_FILE': + return 'NO_PERMISSIONS_TO_READ_FILE'; + case 'PROXY_AUTHENTICATION': + return 'PROXY_AUTHENTICATION'; + case 'REDIRECT_FAILED': + return 'REDIRECT_FAILED'; + case 'REDIRECT_TO_NON_EXISTENT_LOCATION': + return 'REDIRECT_TO_NON_EXISTENT_LOCATION'; + case 'REQUEST_BODY_STREAM_EXHAUSTED': + return 'REQUEST_BODY_STREAM_EXHAUSTED'; + case 'RESET': + return 'RESET'; + case 'RESOURCE_UNAVAILABLE': + return 'RESOURCE_UNAVAILABLE'; + case 'SECURE_CONNECTION_FAILED': + return 'SECURE_CONNECTION_FAILED'; + case 'SERVER_CERTIFICATE_HAS_BAD_DATE': + return 'SERVER_CERTIFICATE_HAS_BAD_DATE'; + case 'SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT': + return 'SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT'; + case 'SERVER_CERTIFICATE_NOT_YET_VALID': + return 'SERVER_CERTIFICATE_NOT_YET_VALID'; + case 'SERVER_CERTIFICATE_UNTRUSTED': + return 'SERVER_CERTIFICATE_UNTRUSTED'; + case 'SERVER_UNREACHABLE': + return 'SERVER_UNREACHABLE'; + case 'TIMEOUT': + return 'TIMEOUT'; + case 'TOO_MANY_REDIRECTS': + return 'TOO_MANY_REDIRECTS'; + case 'TOO_MANY_REQUESTS': + return 'TOO_MANY_REQUESTS'; + case 'UNEXPECTED_ERROR': + return 'UNEXPECTED_ERROR'; + case 'UNKNOWN': + return 'UNKNOWN'; + case 'UNSAFE_RESOURCE': + return 'UNSAFE_RESOURCE'; + case 'UNSUPPORTED_AUTH_SCHEME': + return 'UNSUPPORTED_AUTH_SCHEME'; + case 'UNSUPPORTED_SCHEME': + return 'UNSUPPORTED_SCHEME'; + case 'USER_AUTHENTICATION_FAILED': + return 'USER_AUTHENTICATION_FAILED'; + case 'USER_AUTHENTICATION_REQUIRED': + return 'USER_AUTHENTICATION_REQUIRED'; + case 'USER_CANCELLED_AUTHENTICATION': + return 'USER_CANCELLED_AUTHENTICATION'; + case 'VALID_PROXY_AUTHENTICATION_REQUIRED': + return 'VALID_PROXY_AUTHENTICATION_REQUIRED'; + case 'ZERO_BYTE_RESOURCE': + return 'ZERO_BYTE_RESOURCE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart index f92abb1a9..8d626b472 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'web_resource_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart index 1ba292dff..449de39c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart @@ -47,7 +47,8 @@ class WebResourceRequest { required this.url}); ///Gets a possible [WebResourceRequest] instance from a [Map] value. - static WebResourceRequest? fromMap(Map? map) { + static WebResourceRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -63,7 +64,7 @@ class WebResourceRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hasGesture": hasGesture, "headers": headers, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart index 8df388d38..680c97e0f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart @@ -1,7 +1,8 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'web_resource_response.g.dart'; ///Class representing a resource response of the `WebView`. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart index 3c6defe30..9546a13e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart @@ -42,12 +42,15 @@ class WebResourceResponse { this.statusCode}); ///Gets a possible [WebResourceResponse] instance from a [Map] value. - static WebResourceResponse? fromMap(Map? map) { + static WebResourceResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebResourceResponse( - data: map['data'], + data: map['data'] != null + ? Uint8List.fromList(map['data'].cast()) + : null, headers: map['headers']?.cast(), reasonPhrase: map['reasonPhrase'], statusCode: map['statusCode'], @@ -58,7 +61,7 @@ class WebResourceResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "contentEncoding": contentEncoding, "contentType": contentType, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart index 2a532c7db..8e458fe40 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_storage/platform_web_storage_manager.dart'; +import 'enum_method.dart'; part 'web_storage_origin.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart index 11178c70b..74aaf01f3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart @@ -20,7 +20,8 @@ class WebStorageOrigin { WebStorageOrigin({this.origin, this.quota, this.usage}); ///Gets a possible [WebStorageOrigin] instance from a [Map] value. - static WebStorageOrigin? fromMap(Map? map) { + static WebStorageOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class WebStorageOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "origin": origin, "quota": quota, @@ -68,7 +69,8 @@ class AndroidWebStorageOrigin { AndroidWebStorageOrigin({this.origin, this.quota, this.usage}); ///Gets a possible [AndroidWebStorageOrigin] instance from a [Map] value. - static AndroidWebStorageOrigin? fromMap(Map? map) { + static AndroidWebStorageOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -81,7 +83,7 @@ class AndroidWebStorageOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "origin": origin, "quota": quota, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart index 2e53815ae..69546d999 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart @@ -58,12 +58,52 @@ class WebStorageType { return null; } + /// Gets a possible [WebStorageType] instance value with name [name]. + /// + /// Goes through [WebStorageType.values] looking for a value with + /// name [name], as reported by [WebStorageType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebStorageType? byName(String? name) { + if (name != null) { + try { + return WebStorageType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebStorageType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebStorageType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'localStorage': + return 'LOCAL_STORAGE'; + case 'sessionStorage': + return 'SESSION_STORAGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart index 90ed353c8..4495e4f90 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'website_data_type.dart'; +import 'enum_method.dart'; part 'website_data_record.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart index fb9a1f3ae..d40a3af01 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart @@ -16,14 +16,20 @@ class WebsiteDataRecord { WebsiteDataRecord({this.dataTypes, this.displayName}); ///Gets a possible [WebsiteDataRecord] instance from a [Map] value. - static WebsiteDataRecord? fromMap(Map? map) { + static WebsiteDataRecord? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebsiteDataRecord( dataTypes: map['dataTypes'] != null - ? Set.from( - map['dataTypes'].map((e) => WebsiteDataType.fromNativeValue(e)!)) + ? Set.from(map['dataTypes'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WebsiteDataType.fromNativeValue(e), + EnumMethod.value => WebsiteDataType.fromValue(e), + EnumMethod.name => WebsiteDataType.byName(e) + }!)) : null, displayName: map['displayName'], ); @@ -31,9 +37,15 @@ class WebsiteDataRecord { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "dataTypes": dataTypes?.map((e) => e.toNativeValue()).toList(), + "dataTypes": dataTypes + ?.map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), "displayName": displayName, }; } @@ -64,14 +76,20 @@ class IOSWKWebsiteDataRecord { IOSWKWebsiteDataRecord({this.dataTypes, this.displayName}); ///Gets a possible [IOSWKWebsiteDataRecord] instance from a [Map] value. - static IOSWKWebsiteDataRecord? fromMap(Map? map) { + static IOSWKWebsiteDataRecord? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKWebsiteDataRecord( dataTypes: map['dataTypes'] != null ? Set.from(map['dataTypes'] - .map((e) => IOSWKWebsiteDataType.fromNativeValue(e)!)) + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + IOSWKWebsiteDataType.fromNativeValue(e), + EnumMethod.value => IOSWKWebsiteDataType.fromValue(e), + EnumMethod.name => IOSWKWebsiteDataType.byName(e) + }!)) : null, displayName: map['displayName'], ); @@ -79,9 +97,15 @@ class IOSWKWebsiteDataRecord { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "dataTypes": dataTypes?.map((e) => e.toNativeValue()).toList(), + "dataTypes": dataTypes + ?.map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), "displayName": displayName, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart index e10da39dd..f19bab6af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart @@ -117,12 +117,70 @@ class WebsiteDataType { return null; } + /// Gets a possible [WebsiteDataType] instance value with name [name]. + /// + /// Goes through [WebsiteDataType.values] looking for a value with + /// name [name], as reported by [WebsiteDataType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebsiteDataType? byName(String? name) { + if (name != null) { + try { + return WebsiteDataType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebsiteDataType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebsiteDataType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALL'; + case 'WKWebsiteDataTypeCookies': + return 'WKWebsiteDataTypeCookies'; + case 'WKWebsiteDataTypeDiskCache': + return 'WKWebsiteDataTypeDiskCache'; + case 'WKWebsiteDataTypeFetchCache': + return 'WKWebsiteDataTypeFetchCache'; + case 'WKWebsiteDataTypeIndexedDBDatabases': + return 'WKWebsiteDataTypeIndexedDBDatabases'; + case 'WKWebsiteDataTypeLocalStorage': + return 'WKWebsiteDataTypeLocalStorage'; + case 'WKWebsiteDataTypeMemoryCache': + return 'WKWebsiteDataTypeMemoryCache'; + case 'WKWebsiteDataTypeOfflineWebApplicationCache': + return 'WKWebsiteDataTypeOfflineWebApplicationCache'; + case 'WKWebsiteDataTypeServiceWorkerRegistrations': + return 'WKWebsiteDataTypeServiceWorkerRegistrations'; + case 'WKWebsiteDataTypeSessionStorage': + return 'WKWebsiteDataTypeSessionStorage'; + case 'WKWebsiteDataTypeWebSQLDatabases': + return 'WKWebsiteDataTypeWebSQLDatabases'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -254,12 +312,71 @@ class IOSWKWebsiteDataType { return null; } + /// Gets a possible [IOSWKWebsiteDataType] instance value with name [name]. + /// + /// Goes through [IOSWKWebsiteDataType.values] looking for a value with + /// name [name], as reported by [IOSWKWebsiteDataType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKWebsiteDataType? byName(String? name) { + if (name != null) { + try { + return IOSWKWebsiteDataType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKWebsiteDataType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKWebsiteDataType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALL'; + case 'WKWebsiteDataTypeCookies': + return 'WKWebsiteDataTypeCookies'; + case 'WKWebsiteDataTypeDiskCache': + return 'WKWebsiteDataTypeDiskCache'; + case 'WKWebsiteDataTypeFetchCache': + return 'WKWebsiteDataTypeFetchCache'; + case 'WKWebsiteDataTypeIndexedDBDatabases': + return 'WKWebsiteDataTypeIndexedDBDatabases'; + case 'WKWebsiteDataTypeLocalStorage': + return 'WKWebsiteDataTypeLocalStorage'; + case 'WKWebsiteDataTypeMemoryCache': + return 'WKWebsiteDataTypeMemoryCache'; + case 'WKWebsiteDataTypeOfflineWebApplicationCache': + return 'WKWebsiteDataTypeOfflineWebApplicationCache'; + case 'WKWebsiteDataTypeServiceWorkerRegistrations': + return 'WKWebsiteDataTypeServiceWorkerRegistrations'; + case 'WKWebsiteDataTypeSessionStorage': + return 'WKWebsiteDataTypeSessionStorage'; + case 'WKWebsiteDataTypeWebSQLDatabases': + return 'WKWebsiteDataTypeWebSQLDatabases'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart new file mode 100644 index 000000000..76e7bca5a --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart @@ -0,0 +1,130 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'webview_interface.g.dart'; + +///Class that represents the interfaces that a WebView can support. +@ExchangeableEnum() +class WebViewInterface_ { + // ignore: unused_field + final String _value; + const WebViewInterface_._internal(this._value); + + static const ICoreWebView2Environment = + const WebViewInterface_._internal("ICoreWebView2Environment"); + static const ICoreWebView2Environment2 = + const WebViewInterface_._internal("ICoreWebView2Environment2"); + static const ICoreWebView2Environment3 = + const WebViewInterface_._internal("ICoreWebView2Environment3"); + static const ICoreWebView2Environment4 = + const WebViewInterface_._internal("ICoreWebView2Environment4"); + static const ICoreWebView2Environment5 = + const WebViewInterface_._internal("ICoreWebView2Environment5"); + static const ICoreWebView2Environment6 = + const WebViewInterface_._internal("ICoreWebView2Environment6"); + static const ICoreWebView2Environment7 = + const WebViewInterface_._internal("ICoreWebView2Environment7"); + static const ICoreWebView2Environment8 = + const WebViewInterface_._internal("ICoreWebView2Environment8"); + static const ICoreWebView2Environment9 = + const WebViewInterface_._internal("ICoreWebView2Environment9"); + static const ICoreWebView2Environment10 = + const WebViewInterface_._internal("ICoreWebView2Environment10"); + static const ICoreWebView2Environment11 = + const WebViewInterface_._internal("ICoreWebView2Environment11"); + static const ICoreWebView2Environment12 = + const WebViewInterface_._internal("ICoreWebView2Environment12"); + static const ICoreWebView2Environment13 = + const WebViewInterface_._internal("ICoreWebView2Environment13"); + static const ICoreWebView2Environment14 = + const WebViewInterface_._internal("ICoreWebView2Environment14"); + + static const ICoreWebView2Controller = + const WebViewInterface_._internal("ICoreWebView2Controller"); + static const ICoreWebView2Controller2 = + const WebViewInterface_._internal("ICoreWebView2Controller2"); + static const ICoreWebView2Controller3 = + const WebViewInterface_._internal("ICoreWebView2Controller3"); + static const ICoreWebView2Controller4 = + const WebViewInterface_._internal("ICoreWebView2Controller4"); + + static const ICoreWebView2CompositionController = + const WebViewInterface_._internal("ICoreWebView2CompositionController"); + static const ICoreWebView2CompositionController2 = + const WebViewInterface_._internal("ICoreWebView2CompositionController2"); + static const ICoreWebView2CompositionController3 = + const WebViewInterface_._internal("ICoreWebView2CompositionController3"); + static const ICoreWebView2CompositionController4 = + const WebViewInterface_._internal("ICoreWebView2CompositionController4"); + + static const ICoreWebView2 = + const WebViewInterface_._internal("ICoreWebView2"); + static const ICoreWebView2_2 = + const WebViewInterface_._internal("ICoreWebView2_2"); + static const ICoreWebView2_3 = + const WebViewInterface_._internal("ICoreWebView2_3"); + static const ICoreWebView2_4 = + const WebViewInterface_._internal("ICoreWebView2_4"); + static const ICoreWebView2_5 = + const WebViewInterface_._internal("ICoreWebView2_5"); + static const ICoreWebView2_6 = + const WebViewInterface_._internal("ICoreWebView2_6"); + static const ICoreWebView2_7 = + const WebViewInterface_._internal("ICoreWebView2_7"); + static const ICoreWebView2_8 = + const WebViewInterface_._internal("ICoreWebView2_8"); + static const ICoreWebView2_9 = + const WebViewInterface_._internal("ICoreWebView2_9"); + static const ICoreWebView2_10 = + const WebViewInterface_._internal("ICoreWebView2_10"); + static const ICoreWebView2_11 = + const WebViewInterface_._internal("ICoreWebView2_11"); + static const ICoreWebView2_12 = + const WebViewInterface_._internal("ICoreWebView2_12"); + static const ICoreWebView2_13 = + const WebViewInterface_._internal("ICoreWebView2_13"); + static const ICoreWebView2_14 = + const WebViewInterface_._internal("ICoreWebView2_14"); + static const ICoreWebView2_15 = + const WebViewInterface_._internal("ICoreWebView2_15"); + static const ICoreWebView2_16 = + const WebViewInterface_._internal("ICoreWebView2_16"); + static const ICoreWebView2_17 = + const WebViewInterface_._internal("ICoreWebView2_17"); + static const ICoreWebView2_18 = + const WebViewInterface_._internal("ICoreWebView2_18"); + static const ICoreWebView2_19 = + const WebViewInterface_._internal("ICoreWebView2_19"); + static const ICoreWebView2_20 = + const WebViewInterface_._internal("ICoreWebView2_20"); + static const ICoreWebView2_21 = + const WebViewInterface_._internal("ICoreWebView2_21"); + static const ICoreWebView2_22 = + const WebViewInterface_._internal("ICoreWebView2_22"); + static const ICoreWebView2_23 = + const WebViewInterface_._internal("ICoreWebView2_23"); + static const ICoreWebView2_24 = + const WebViewInterface_._internal("ICoreWebView2_24"); + static const ICoreWebView2_25 = + const WebViewInterface_._internal("ICoreWebView2_25"); + static const ICoreWebView2_26 = + const WebViewInterface_._internal("ICoreWebView2_26"); + + static const ICoreWebView2Settings = + const WebViewInterface_._internal("ICoreWebView2Settings"); + static const ICoreWebView2Settings2 = + const WebViewInterface_._internal("ICoreWebView2Settings2"); + static const ICoreWebView2Settings3 = + const WebViewInterface_._internal("ICoreWebView2Settings3"); + static const ICoreWebView2Settings4 = + const WebViewInterface_._internal("ICoreWebView2Settings4"); + static const ICoreWebView2Settings5 = + const WebViewInterface_._internal("ICoreWebView2Settings5"); + static const ICoreWebView2Settings6 = + const WebViewInterface_._internal("ICoreWebView2Settings6"); + static const ICoreWebView2Settings7 = + const WebViewInterface_._internal("ICoreWebView2Settings7"); + static const ICoreWebView2Settings8 = + const WebViewInterface_._internal("ICoreWebView2Settings8"); + static const ICoreWebView2Settings9 = + const WebViewInterface_._internal("ICoreWebView2Settings9"); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart new file mode 100644 index 000000000..a3cdfd661 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart @@ -0,0 +1,391 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'webview_interface.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class that represents the interfaces that a WebView can support. +class WebViewInterface { + final String _value; + final String _nativeValue; + const WebViewInterface._internal(this._value, this._nativeValue); +// ignore: unused_element + factory WebViewInterface._internalMultiPlatform( + String value, Function nativeValue) => + WebViewInterface._internal(value, nativeValue()); + static const ICoreWebView2 = + WebViewInterface._internal('ICoreWebView2', 'ICoreWebView2'); + static const ICoreWebView2CompositionController = WebViewInterface._internal( + 'ICoreWebView2CompositionController', + 'ICoreWebView2CompositionController'); + static const ICoreWebView2CompositionController2 = WebViewInterface._internal( + 'ICoreWebView2CompositionController2', + 'ICoreWebView2CompositionController2'); + static const ICoreWebView2CompositionController3 = WebViewInterface._internal( + 'ICoreWebView2CompositionController3', + 'ICoreWebView2CompositionController3'); + static const ICoreWebView2CompositionController4 = WebViewInterface._internal( + 'ICoreWebView2CompositionController4', + 'ICoreWebView2CompositionController4'); + static const ICoreWebView2Controller = WebViewInterface._internal( + 'ICoreWebView2Controller', 'ICoreWebView2Controller'); + static const ICoreWebView2Controller2 = WebViewInterface._internal( + 'ICoreWebView2Controller2', 'ICoreWebView2Controller2'); + static const ICoreWebView2Controller3 = WebViewInterface._internal( + 'ICoreWebView2Controller3', 'ICoreWebView2Controller3'); + static const ICoreWebView2Controller4 = WebViewInterface._internal( + 'ICoreWebView2Controller4', 'ICoreWebView2Controller4'); + static const ICoreWebView2Environment = WebViewInterface._internal( + 'ICoreWebView2Environment', 'ICoreWebView2Environment'); + static const ICoreWebView2Environment10 = WebViewInterface._internal( + 'ICoreWebView2Environment10', 'ICoreWebView2Environment10'); + static const ICoreWebView2Environment11 = WebViewInterface._internal( + 'ICoreWebView2Environment11', 'ICoreWebView2Environment11'); + static const ICoreWebView2Environment12 = WebViewInterface._internal( + 'ICoreWebView2Environment12', 'ICoreWebView2Environment12'); + static const ICoreWebView2Environment13 = WebViewInterface._internal( + 'ICoreWebView2Environment13', 'ICoreWebView2Environment13'); + static const ICoreWebView2Environment14 = WebViewInterface._internal( + 'ICoreWebView2Environment14', 'ICoreWebView2Environment14'); + static const ICoreWebView2Environment2 = WebViewInterface._internal( + 'ICoreWebView2Environment2', 'ICoreWebView2Environment2'); + static const ICoreWebView2Environment3 = WebViewInterface._internal( + 'ICoreWebView2Environment3', 'ICoreWebView2Environment3'); + static const ICoreWebView2Environment4 = WebViewInterface._internal( + 'ICoreWebView2Environment4', 'ICoreWebView2Environment4'); + static const ICoreWebView2Environment5 = WebViewInterface._internal( + 'ICoreWebView2Environment5', 'ICoreWebView2Environment5'); + static const ICoreWebView2Environment6 = WebViewInterface._internal( + 'ICoreWebView2Environment6', 'ICoreWebView2Environment6'); + static const ICoreWebView2Environment7 = WebViewInterface._internal( + 'ICoreWebView2Environment7', 'ICoreWebView2Environment7'); + static const ICoreWebView2Environment8 = WebViewInterface._internal( + 'ICoreWebView2Environment8', 'ICoreWebView2Environment8'); + static const ICoreWebView2Environment9 = WebViewInterface._internal( + 'ICoreWebView2Environment9', 'ICoreWebView2Environment9'); + static const ICoreWebView2Settings = WebViewInterface._internal( + 'ICoreWebView2Settings', 'ICoreWebView2Settings'); + static const ICoreWebView2Settings2 = WebViewInterface._internal( + 'ICoreWebView2Settings2', 'ICoreWebView2Settings2'); + static const ICoreWebView2Settings3 = WebViewInterface._internal( + 'ICoreWebView2Settings3', 'ICoreWebView2Settings3'); + static const ICoreWebView2Settings4 = WebViewInterface._internal( + 'ICoreWebView2Settings4', 'ICoreWebView2Settings4'); + static const ICoreWebView2Settings5 = WebViewInterface._internal( + 'ICoreWebView2Settings5', 'ICoreWebView2Settings5'); + static const ICoreWebView2Settings6 = WebViewInterface._internal( + 'ICoreWebView2Settings6', 'ICoreWebView2Settings6'); + static const ICoreWebView2Settings7 = WebViewInterface._internal( + 'ICoreWebView2Settings7', 'ICoreWebView2Settings7'); + static const ICoreWebView2Settings8 = WebViewInterface._internal( + 'ICoreWebView2Settings8', 'ICoreWebView2Settings8'); + static const ICoreWebView2Settings9 = WebViewInterface._internal( + 'ICoreWebView2Settings9', 'ICoreWebView2Settings9'); + static const ICoreWebView2_10 = + WebViewInterface._internal('ICoreWebView2_10', 'ICoreWebView2_10'); + static const ICoreWebView2_11 = + WebViewInterface._internal('ICoreWebView2_11', 'ICoreWebView2_11'); + static const ICoreWebView2_12 = + WebViewInterface._internal('ICoreWebView2_12', 'ICoreWebView2_12'); + static const ICoreWebView2_13 = + WebViewInterface._internal('ICoreWebView2_13', 'ICoreWebView2_13'); + static const ICoreWebView2_14 = + WebViewInterface._internal('ICoreWebView2_14', 'ICoreWebView2_14'); + static const ICoreWebView2_15 = + WebViewInterface._internal('ICoreWebView2_15', 'ICoreWebView2_15'); + static const ICoreWebView2_16 = + WebViewInterface._internal('ICoreWebView2_16', 'ICoreWebView2_16'); + static const ICoreWebView2_17 = + WebViewInterface._internal('ICoreWebView2_17', 'ICoreWebView2_17'); + static const ICoreWebView2_18 = + WebViewInterface._internal('ICoreWebView2_18', 'ICoreWebView2_18'); + static const ICoreWebView2_19 = + WebViewInterface._internal('ICoreWebView2_19', 'ICoreWebView2_19'); + static const ICoreWebView2_2 = + WebViewInterface._internal('ICoreWebView2_2', 'ICoreWebView2_2'); + static const ICoreWebView2_20 = + WebViewInterface._internal('ICoreWebView2_20', 'ICoreWebView2_20'); + static const ICoreWebView2_21 = + WebViewInterface._internal('ICoreWebView2_21', 'ICoreWebView2_21'); + static const ICoreWebView2_22 = + WebViewInterface._internal('ICoreWebView2_22', 'ICoreWebView2_22'); + static const ICoreWebView2_23 = + WebViewInterface._internal('ICoreWebView2_23', 'ICoreWebView2_23'); + static const ICoreWebView2_24 = + WebViewInterface._internal('ICoreWebView2_24', 'ICoreWebView2_24'); + static const ICoreWebView2_25 = + WebViewInterface._internal('ICoreWebView2_25', 'ICoreWebView2_25'); + static const ICoreWebView2_26 = + WebViewInterface._internal('ICoreWebView2_26', 'ICoreWebView2_26'); + static const ICoreWebView2_3 = + WebViewInterface._internal('ICoreWebView2_3', 'ICoreWebView2_3'); + static const ICoreWebView2_4 = + WebViewInterface._internal('ICoreWebView2_4', 'ICoreWebView2_4'); + static const ICoreWebView2_5 = + WebViewInterface._internal('ICoreWebView2_5', 'ICoreWebView2_5'); + static const ICoreWebView2_6 = + WebViewInterface._internal('ICoreWebView2_6', 'ICoreWebView2_6'); + static const ICoreWebView2_7 = + WebViewInterface._internal('ICoreWebView2_7', 'ICoreWebView2_7'); + static const ICoreWebView2_8 = + WebViewInterface._internal('ICoreWebView2_8', 'ICoreWebView2_8'); + static const ICoreWebView2_9 = + WebViewInterface._internal('ICoreWebView2_9', 'ICoreWebView2_9'); + + ///Set of all values of [WebViewInterface]. + static final Set values = [ + WebViewInterface.ICoreWebView2, + WebViewInterface.ICoreWebView2CompositionController, + WebViewInterface.ICoreWebView2CompositionController2, + WebViewInterface.ICoreWebView2CompositionController3, + WebViewInterface.ICoreWebView2CompositionController4, + WebViewInterface.ICoreWebView2Controller, + WebViewInterface.ICoreWebView2Controller2, + WebViewInterface.ICoreWebView2Controller3, + WebViewInterface.ICoreWebView2Controller4, + WebViewInterface.ICoreWebView2Environment, + WebViewInterface.ICoreWebView2Environment10, + WebViewInterface.ICoreWebView2Environment11, + WebViewInterface.ICoreWebView2Environment12, + WebViewInterface.ICoreWebView2Environment13, + WebViewInterface.ICoreWebView2Environment14, + WebViewInterface.ICoreWebView2Environment2, + WebViewInterface.ICoreWebView2Environment3, + WebViewInterface.ICoreWebView2Environment4, + WebViewInterface.ICoreWebView2Environment5, + WebViewInterface.ICoreWebView2Environment6, + WebViewInterface.ICoreWebView2Environment7, + WebViewInterface.ICoreWebView2Environment8, + WebViewInterface.ICoreWebView2Environment9, + WebViewInterface.ICoreWebView2Settings, + WebViewInterface.ICoreWebView2Settings2, + WebViewInterface.ICoreWebView2Settings3, + WebViewInterface.ICoreWebView2Settings4, + WebViewInterface.ICoreWebView2Settings5, + WebViewInterface.ICoreWebView2Settings6, + WebViewInterface.ICoreWebView2Settings7, + WebViewInterface.ICoreWebView2Settings8, + WebViewInterface.ICoreWebView2Settings9, + WebViewInterface.ICoreWebView2_10, + WebViewInterface.ICoreWebView2_11, + WebViewInterface.ICoreWebView2_12, + WebViewInterface.ICoreWebView2_13, + WebViewInterface.ICoreWebView2_14, + WebViewInterface.ICoreWebView2_15, + WebViewInterface.ICoreWebView2_16, + WebViewInterface.ICoreWebView2_17, + WebViewInterface.ICoreWebView2_18, + WebViewInterface.ICoreWebView2_19, + WebViewInterface.ICoreWebView2_2, + WebViewInterface.ICoreWebView2_20, + WebViewInterface.ICoreWebView2_21, + WebViewInterface.ICoreWebView2_22, + WebViewInterface.ICoreWebView2_23, + WebViewInterface.ICoreWebView2_24, + WebViewInterface.ICoreWebView2_25, + WebViewInterface.ICoreWebView2_26, + WebViewInterface.ICoreWebView2_3, + WebViewInterface.ICoreWebView2_4, + WebViewInterface.ICoreWebView2_5, + WebViewInterface.ICoreWebView2_6, + WebViewInterface.ICoreWebView2_7, + WebViewInterface.ICoreWebView2_8, + WebViewInterface.ICoreWebView2_9, + ].toSet(); + + ///Gets a possible [WebViewInterface] instance from [String] value. + static WebViewInterface? fromValue(String? value) { + if (value != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [WebViewInterface] instance from a native value. + static WebViewInterface? fromNativeValue(String? value) { + if (value != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [WebViewInterface] instance value with name [name]. + /// + /// Goes through [WebViewInterface.values] looking for a value with + /// name [name], as reported by [WebViewInterface.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebViewInterface? byName(String? name) { + if (name != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebViewInterface] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebViewInterface.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [String] native value. + String toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'ICoreWebView2': + return 'ICoreWebView2'; + case 'ICoreWebView2CompositionController': + return 'ICoreWebView2CompositionController'; + case 'ICoreWebView2CompositionController2': + return 'ICoreWebView2CompositionController2'; + case 'ICoreWebView2CompositionController3': + return 'ICoreWebView2CompositionController3'; + case 'ICoreWebView2CompositionController4': + return 'ICoreWebView2CompositionController4'; + case 'ICoreWebView2Controller': + return 'ICoreWebView2Controller'; + case 'ICoreWebView2Controller2': + return 'ICoreWebView2Controller2'; + case 'ICoreWebView2Controller3': + return 'ICoreWebView2Controller3'; + case 'ICoreWebView2Controller4': + return 'ICoreWebView2Controller4'; + case 'ICoreWebView2Environment': + return 'ICoreWebView2Environment'; + case 'ICoreWebView2Environment10': + return 'ICoreWebView2Environment10'; + case 'ICoreWebView2Environment11': + return 'ICoreWebView2Environment11'; + case 'ICoreWebView2Environment12': + return 'ICoreWebView2Environment12'; + case 'ICoreWebView2Environment13': + return 'ICoreWebView2Environment13'; + case 'ICoreWebView2Environment14': + return 'ICoreWebView2Environment14'; + case 'ICoreWebView2Environment2': + return 'ICoreWebView2Environment2'; + case 'ICoreWebView2Environment3': + return 'ICoreWebView2Environment3'; + case 'ICoreWebView2Environment4': + return 'ICoreWebView2Environment4'; + case 'ICoreWebView2Environment5': + return 'ICoreWebView2Environment5'; + case 'ICoreWebView2Environment6': + return 'ICoreWebView2Environment6'; + case 'ICoreWebView2Environment7': + return 'ICoreWebView2Environment7'; + case 'ICoreWebView2Environment8': + return 'ICoreWebView2Environment8'; + case 'ICoreWebView2Environment9': + return 'ICoreWebView2Environment9'; + case 'ICoreWebView2Settings': + return 'ICoreWebView2Settings'; + case 'ICoreWebView2Settings2': + return 'ICoreWebView2Settings2'; + case 'ICoreWebView2Settings3': + return 'ICoreWebView2Settings3'; + case 'ICoreWebView2Settings4': + return 'ICoreWebView2Settings4'; + case 'ICoreWebView2Settings5': + return 'ICoreWebView2Settings5'; + case 'ICoreWebView2Settings6': + return 'ICoreWebView2Settings6'; + case 'ICoreWebView2Settings7': + return 'ICoreWebView2Settings7'; + case 'ICoreWebView2Settings8': + return 'ICoreWebView2Settings8'; + case 'ICoreWebView2Settings9': + return 'ICoreWebView2Settings9'; + case 'ICoreWebView2_10': + return 'ICoreWebView2_10'; + case 'ICoreWebView2_11': + return 'ICoreWebView2_11'; + case 'ICoreWebView2_12': + return 'ICoreWebView2_12'; + case 'ICoreWebView2_13': + return 'ICoreWebView2_13'; + case 'ICoreWebView2_14': + return 'ICoreWebView2_14'; + case 'ICoreWebView2_15': + return 'ICoreWebView2_15'; + case 'ICoreWebView2_16': + return 'ICoreWebView2_16'; + case 'ICoreWebView2_17': + return 'ICoreWebView2_17'; + case 'ICoreWebView2_18': + return 'ICoreWebView2_18'; + case 'ICoreWebView2_19': + return 'ICoreWebView2_19'; + case 'ICoreWebView2_2': + return 'ICoreWebView2_2'; + case 'ICoreWebView2_20': + return 'ICoreWebView2_20'; + case 'ICoreWebView2_21': + return 'ICoreWebView2_21'; + case 'ICoreWebView2_22': + return 'ICoreWebView2_22'; + case 'ICoreWebView2_23': + return 'ICoreWebView2_23'; + case 'ICoreWebView2_24': + return 'ICoreWebView2_24'; + case 'ICoreWebView2_25': + return 'ICoreWebView2_25'; + case 'ICoreWebView2_26': + return 'ICoreWebView2_26'; + case 'ICoreWebView2_3': + return 'ICoreWebView2_3'; + case 'ICoreWebView2_4': + return 'ICoreWebView2_4'; + case 'ICoreWebView2_5': + return 'ICoreWebView2_5'; + case 'ICoreWebView2_6': + return 'ICoreWebView2_6'; + case 'ICoreWebView2_7': + return 'ICoreWebView2_7'; + case 'ICoreWebView2_8': + return 'ICoreWebView2_8'; + case 'ICoreWebView2_9': + return 'ICoreWebView2_9'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart index ec5985d15..8940a25e3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'webview_package_info.g.dart'; ///Class that represents a `WebView` package info. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart index e1b2c3e8c..22fb70ec6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart @@ -16,7 +16,8 @@ class WebViewPackageInfo { WebViewPackageInfo({this.packageName, this.versionName}); ///Gets a possible [WebViewPackageInfo] instance from a [Map] value. - static WebViewPackageInfo? fromMap(Map? map) { + static WebViewPackageInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class WebViewPackageInfo { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "packageName": packageName, "versionName": versionName, @@ -58,7 +59,8 @@ class AndroidWebViewPackageInfo { AndroidWebViewPackageInfo({this.packageName, this.versionName}); ///Gets a possible [AndroidWebViewPackageInfo] instance from a [Map] value. - static AndroidWebViewPackageInfo? fromMap(Map? map) { + static AndroidWebViewPackageInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -70,7 +72,7 @@ class AndroidWebViewPackageInfo { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "packageName": packageName, "versionName": versionName, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart index 1fca268bf..f88d02916 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart @@ -11,5 +11,8 @@ class WebViewRenderProcessAction_ { const WebViewRenderProcessAction_._internal(this._value); ///Cause this renderer to terminate. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + ]) static const TERMINATE = const WebViewRenderProcessAction_._internal(0); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart index 15542077b..73494b545 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart @@ -18,6 +18,9 @@ class WebViewRenderProcessAction { WebViewRenderProcessAction._internal(value, nativeValue()); ///Cause this renderer to terminate. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView static const TERMINATE = WebViewRenderProcessAction._internal(0, 0); ///Set of all values of [WebViewRenderProcessAction]. @@ -51,12 +54,52 @@ class WebViewRenderProcessAction { return null; } + /// Gets a possible [WebViewRenderProcessAction] instance value with name [name]. + /// + /// Goes through [WebViewRenderProcessAction.values] looking for a value with + /// name [name], as reported by [WebViewRenderProcessAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebViewRenderProcessAction? byName(String? name) { + if (name != null) { + try { + return WebViewRenderProcessAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebViewRenderProcessAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebViewRenderProcessAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'TERMINATE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -65,10 +108,6 @@ class WebViewRenderProcessAction { @override String toString() { - switch (_value) { - case 0: - return 'TERMINATE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart index 9b72119f7..deba724d0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'window_features.g.dart'; ///Class that specifies optional attributes for the containing window when a new web view is requested. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart index 431416053..248d7a9c3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart @@ -42,7 +42,8 @@ class WindowFeatures { this.y}); ///Gets a possible [WindowFeatures] instance from a [Map] value. - static WindowFeatures? fromMap(Map? map) { + static WindowFeatures? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -60,7 +61,7 @@ class WindowFeatures { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsResizing": allowsResizing, "height": height, @@ -122,7 +123,8 @@ class IOSWKWindowFeatures { this.y}); ///Gets a possible [IOSWKWindowFeatures] instance from a [Map] value. - static IOSWKWindowFeatures? fromMap(Map? map) { + static IOSWKWindowFeatures? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -140,7 +142,7 @@ class IOSWKWindowFeatures { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsResizing": allowsResizing, "height": height, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart index cde3470c2..159f07f26 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart @@ -216,26 +216,43 @@ class WindowStyleMask { return null; } + /// Gets a possible [WindowStyleMask] instance value with name [name]. + /// + /// Goes through [WindowStyleMask.values] looking for a value with + /// name [name], as reported by [WindowStyleMask.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowStyleMask? byName(String? name) { + if (name != null) { + try { + return WindowStyleMask.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowStyleMask] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WindowStyleMask.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - WindowStyleMask operator |(WindowStyleMask value) => - WindowStyleMask._internal( - value.toValue() | _value, - value.toNativeValue() != null && _nativeValue != null - ? value.toNativeValue()! | _nativeValue! - : _nativeValue); - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'BORDERLESS'; @@ -262,4 +279,21 @@ class WindowStyleMask { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + WindowStyleMask operator |(WindowStyleMask value) => + WindowStyleMask._internal( + value.toValue() | _value, + value.toNativeValue() != null && _nativeValue != null + ? value.toNativeValue()! | _nativeValue! + : _nativeValue); + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart index 543e4e157..c2e41fa62 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart @@ -110,20 +110,45 @@ class WindowTitlebarSeparatorStyle { return null; } + /// Gets a possible [WindowTitlebarSeparatorStyle] instance value with name [name]. + /// + /// Goes through [WindowTitlebarSeparatorStyle.values] looking for a value with + /// name [name], as reported by [WindowTitlebarSeparatorStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowTitlebarSeparatorStyle? byName(String? name) { + if (name != null) { + try { + return WindowTitlebarSeparatorStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowTitlebarSeparatorStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WindowTitlebarSeparatorStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'AUTOMATIC'; @@ -136,4 +161,15 @@ class WindowTitlebarSeparatorStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart index 2d49b9c9c..cc4902067 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart @@ -12,11 +12,17 @@ class WindowType_ { const WindowType_._internal(this._value); ///Adds the new browser window as a separate new window from the main window. - @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'WINDOW')]) + @EnumSupportedPlatforms(platforms: [ + EnumMacOSPlatform(value: 'WINDOW'), + EnumWindowsPlatform(value: 'WINDOW') + ]) static const WINDOW = const WindowType_._internal('WINDOW'); ///Adds the new browser window as a child window of the main window. - @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'CHILD')]) + @EnumSupportedPlatforms(platforms: [ + EnumMacOSPlatform(value: 'CHILD'), + EnumWindowsPlatform(value: 'CHILD') + ]) static const CHILD = const WindowType_._internal('CHILD'); ///Adds the new browser window as a new tab in a tabbed window of the main window. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart index b9be001c7..e1628c9fe 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart @@ -20,10 +20,13 @@ class WindowType { /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows static final CHILD = WindowType._internalMultiPlatform('CHILD', () { switch (defaultTargetPlatform) { case TargetPlatform.macOS: return 'CHILD'; + case TargetPlatform.windows: + return 'CHILD'; default: break; } @@ -48,10 +51,13 @@ class WindowType { /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows static final WINDOW = WindowType._internalMultiPlatform('WINDOW', () { switch (defaultTargetPlatform) { case TargetPlatform.macOS: return 'WINDOW'; + case TargetPlatform.windows: + return 'WINDOW'; default: break; } @@ -91,12 +97,54 @@ class WindowType { return null; } + /// Gets a possible [WindowType] instance value with name [name]. + /// + /// Goes through [WindowType.values] looking for a value with + /// name [name], as reported by [WindowType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowType? byName(String? name) { + if (name != null) { + try { + return WindowType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WindowType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CHILD': + return 'CHILD'; + case 'TABBED': + return 'TABBED'; + case 'WINDOW': + return 'WINDOW'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/util.dart b/flutter_inappwebview_platform_interface/lib/src/util.dart index 113ccf782..a6b780bc0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/util.dart +++ b/flutter_inappwebview_platform_interface/lib/src/util.dart @@ -585,7 +585,7 @@ class Color_ extends Color { Color_(int value) : super(value); } -abstract class ChannelController implements Disposable { +abstract mixin class ChannelController implements Disposable { MethodChannel? _channel; Future Function(MethodCall call)? _handler; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart index 7ce0505ff..226c6bd17 100755 --- a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import 'platform_web_authenticate_session.dart'; +import '../types/enum_method.dart'; part 'web_authenticate_session_settings.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart index e94d4745b..4b0d77613 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart @@ -31,7 +31,8 @@ class WebAuthenticationSessionSettings { {this.prefersEphemeralWebBrowserSession = false}); ///Gets a possible [WebAuthenticationSessionSettings] instance from a [Map] value. - static WebAuthenticationSessionSettings? fromMap(Map? map) { + static WebAuthenticationSessionSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart index 7265aa320..35210403e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../inappwebview_platform.dart'; +import '../types/enum_method.dart'; import '../types/web_message_callback.dart'; import 'web_message.dart'; @@ -103,7 +104,7 @@ abstract class IWebMessagePort { 'close is not implemented on the current platform'); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { throw UnimplementedError( 'toMap is not implemented on the current platform'); } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart index 33476b95c..df817e7e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../platform_webview_feature.dart'; import 'platform_web_message_port.dart'; +import '../types/enum_method.dart'; part 'web_message.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart index facac8e4c..f3bf55b2a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart @@ -56,12 +56,52 @@ class WebMessageType { return null; } + /// Gets a possible [WebMessageType] instance value with name [name]. + /// + /// Goes through [WebMessageType.values] looking for a value with + /// name [name], as reported by [WebMessageType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebMessageType? byName(String? name) { + if (name != null) { + try { + return WebMessageType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebMessageType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebMessageType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ARRAY_BUFFER'; + case 0: + return 'STRING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -70,13 +110,7 @@ class WebMessageType { @override String toString() { - switch (_value) { - case 1: - return 'ARRAY_BUFFER'; - case 0: - return 'STRING'; - } - return _value.toString(); + return name(); } } @@ -104,7 +138,8 @@ class WebMessage { } ///Gets a possible [WebMessage] instance from a [Map] value. - static WebMessage? fromMap(Map? map) { + static WebMessage? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -113,17 +148,25 @@ class WebMessage { ports: map['ports'] != null ? List.from(map['ports'].map((e) => e)) : null, - type: WebMessageType.fromNativeValue(map['type'])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WebMessageType.fromNativeValue(map['type']), + EnumMethod.value => WebMessageType.fromValue(map['type']), + EnumMethod.name => WebMessageType.byName(map['type']) + }!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "data": data, - "ports": ports?.map((e) => e.toMap()).toList(), - "type": type.toNativeValue(), + "ports": ports?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart index 84ecd089f..dcd40e254 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart @@ -33,6 +33,7 @@ class PlatformWebStorageCreationParams { ///- iOS ///- MacOS ///- Web +///- Windows ///{@endtemplate} abstract class PlatformWebStorage extends PlatformInterface implements Disposable { @@ -103,7 +104,7 @@ class PlatformStorageCreationParams { ///Class that provides methods to manage the JavaScript [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) object. ///It is used by [PlatformLocalStorage] and [PlatformSessionStorage]. ///{@endtemplate} -abstract class PlatformStorage implements Disposable { +abstract mixin class PlatformStorage implements Disposable { ///{@template flutter_inappwebview_platform_interface.PlatformStorage.controller} ///Controller used to interact with storage. ///{@endtemplate} diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart index 4a2e3bce1..1e1fc0957 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import '../types/enum_method.dart'; + part 'web_storage_item.g.dart'; ///Class that represents a single web storage item of the JavaScript `window.sessionStorage` and `window.localStorage` objects. diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart index ec2f686b2..b6a6a9088 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart @@ -16,7 +16,8 @@ class WebStorageItem { WebStorageItem({this.key, this.value}); ///Gets a possible [WebStorageItem] instance from a [Map] value. - static WebStorageItem? fromMap(Map? map) { + static WebStorageItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class WebStorageItem { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "key": key, "value": value, diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart new file mode 100644 index 000000000..af86a742b --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart @@ -0,0 +1,2 @@ +export 'platform_webview_environment.dart'; +export 'webview_environment_settings.dart' show WebViewEnvironmentSettings; diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart new file mode 100644 index 000000000..6e4d5e482 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart @@ -0,0 +1,258 @@ +import 'package:flutter/foundation.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../debug_logging_settings.dart'; +import '../inappwebview_platform.dart'; +import '../in_app_webview/platform_webview.dart'; +import '../types/browser_process_info.dart'; +import '../types/disposable.dart'; +import '../types/browser_process_exited_detail.dart'; +import '../types/browser_process_infos_changed_detail.dart'; +import '../types/webview_interface.dart'; +import 'webview_environment_settings.dart'; + +/// Object specifying creation parameters for creating a [PlatformWebViewEnvironment]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +@immutable +class PlatformWebViewEnvironmentCreationParams { + /// Used by the platform implementation to create a new [PlatformWebViewEnvironment]. + const PlatformWebViewEnvironmentCreationParams({this.settings}); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + final WebViewEnvironmentSettings? settings; +} + +///Controls a WebView Environment used by WebView instances. +///Use [dispose] when not needed anymore to release references. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +abstract class PlatformWebViewEnvironment extends PlatformInterface + implements Disposable { + ///Debug settings used by [PlatformWebViewEnvironment]. + static DebugLoggingSettings debugLoggingSettings = + DebugLoggingSettings(maxLogMessageLength: 1000); + + /// Creates a new [PlatformInAppWebViewController] + factory PlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params) { + assert( + InAppWebViewPlatform.instance != null, + 'A platform implementation for `flutter_inappwebview` has not been set. Please ' + 'ensure that an implementation of `InAppWebViewPlatform` has been set to ' + '`InAppWebViewPlatform.instance` before use. For unit testing, ' + '`InAppWebViewPlatform.instance` can be set with your own test implementation.', + ); + final PlatformWebViewEnvironment webViewEnvironment = + InAppWebViewPlatform.instance!.createPlatformWebViewEnvironment(params); + PlatformInterface.verify(webViewEnvironment, _token); + return webViewEnvironment; + } + + /// Creates a new [PlatformWebViewEnvironment] to access static methods. + factory PlatformWebViewEnvironment.static() { + assert( + InAppWebViewPlatform.instance != null, + 'A platform implementation for `flutter_inappwebview` has not been set. Please ' + 'ensure that an implementation of `InAppWebViewPlatform` has been set to ' + '`InAppWebViewPlatform.instance` before use. For unit testing, ' + '`InAppWebViewPlatform.instance` can be set with your own test implementation.', + ); + final PlatformWebViewEnvironment webViewEnvironment = + InAppWebViewPlatform.instance!.createPlatformWebViewEnvironmentStatic(); + PlatformInterface.verify(webViewEnvironment, _token); + return webViewEnvironment; + } + + /// Used by the platform implementation to create a new [PlatformWebViewEnvironment]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformWebViewEnvironment.implementation(this.params) : super(token: _token); + + static final Object _token = Object(); + + /// The parameters used to initialize the [PlatformWebViewEnvironment]. + final PlatformWebViewEnvironmentCreationParams params; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.id} + /// WebView Environment ID. + ///{@endtemplate} + String get id => + throw UnimplementedError('id is not implemented on the current platform'); + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + /// WebView Environment settings. + ///{@endtemplate} + WebViewEnvironmentSettings? get settings => params.settings; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isInterfaceSupported} + ///Returns `true` if the WebView Environment supports the specified [interface], otherwise `false`. + ///Only the ones related to [WebViewInterface.ICoreWebView2Environment] are valid interfaces to check; + ///otherwise, it will always return `false`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ///{@endtemplate} + Future isInterfaceSupported(WebViewInterface interface) async { + throw UnimplementedError( + 'isInterfaceSupported is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + ///Returns a list of all process using same user data folder except for crashpad process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1108.44+ ([Official API - ICoreWebView2Environment8.GetProcessInfos](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment8?view=webview2-1.0.2849.39#getprocessinfos)) + ///{@endtemplate} + Future> getProcessInfos() async { + throw UnimplementedError( + 'getProcessInfos is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + ///Returns the path of the folder where minidump files are written. + /// + ///Whenever a WebView2 process crashes, a crash dump file will be created in the crash dump folder. + ///The crash dump format is minidump files. + ///Please see [Minidump Files documentation](https://learn.microsoft.com/en-us/windows/win32/debug/minidump-files) for detailed information. + ///Normally when a single child process fails, a minidump will be generated and written to disk, + ///then the [PlatformWebViewCreationParams.onProcessFailed] event is raised. + ///But for unexpected crashes, a minidump file might + ///not be generated at all, despite whether [PlatformWebViewCreationParams.onProcessFailed] event is raised. + ///If there are multiple process failures at once, multiple minidump files could be generated. + ///Thus [getFailureReportFolderPath] could contain old minidump files that are + ///not associated with a specific [PlatformWebViewCreationParams.onProcessFailed] event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1518.46+ ([Official API - ICoreWebView2Environment11.get_FailureReportFolderPath](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment11?view=webview2-1.0.2849.39#get_failurereportfolderpath)) + ///{@endtemplate} + Future getFailureReportFolderPath() async { + throw UnimplementedError( + 'getFailureReportFolderPath is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} + ///Creates the [PlatformWebViewEnvironment] using [settings]. + /// + ///Check https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions + ///for more info. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + ///{@endtemplate} + Future create( + {WebViewEnvironmentSettings? settings}) { + throw UnimplementedError( + 'create is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + ///Get the browser version info including channel name if it is not the WebView2 Runtime. + /// + ///Channel names are Beta, Dev, and Canary. + ///If an override exists for the browserExecutableFolder or the channel preference, the override is used. + ///If an override is not specified, then the parameter value passed to [getAvailableVersion] is used. + ///Returns `null` if it fails to find an installed WebView2 runtime or non-stable Microsoft Edge installation. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - GetAvailableCoreWebView2BrowserVersionString](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#comparebrowserversions)) + ///{@endtemplate} + Future getAvailableVersion({String? browserExecutableFolder}) { + throw UnimplementedError( + 'getAvailableVersion is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.compareBrowserVersions} + ///This method is for anyone want to compare version correctly to determine which version is newer, older or same. + /// + ///Use it to determine whether to use webview2 or certain feature based upon version. + ///Sets the value of result to `-1`, `0` or `1` if version1 is less than, equal or greater than version2 respectively. + ///Returns `null` if it fails to parse any of the version strings. + ///Directly use the version info obtained from [getAvailableVersion] with input, channel information is ignored. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CompareBrowserVersions](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#comparebrowserversions)) + ///{@endtemplate} + Future compareBrowserVersions( + {required String version1, required String version2}) { + throw UnimplementedError( + 'compareBrowserVersions is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onNewBrowserVersionAvailable} + ///[onNewBrowserVersionAvailable] runs when a newer version of the WebView2 Runtime + ///is installed and available using WebView2. + ///To use the newer version of the browser you must create a new [PlatformWebViewEnvironment] and WebView. + ///The event only runs for new version from the same WebView2 Runtime from which the code is running. + ///When not running with installed WebView2 Runtime, no event is run. + /// + ///Because a user data folder is only able to be used by one browser process at a time, + ///if you want to use the same user data folder in the WebView using the new version of the browser, + ///you must close the environment and instance of WebView that are using the older version of the browser first. + ///Or simply prompt the user to restart the app. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2Environment.add_NewBrowserVersionAvailable](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.2849.39#add_newbrowserversionavailable)) + ///{@endtemplate} + void Function()? onNewBrowserVersionAvailable; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onBrowserProcessExited} + ///The [onBrowserProcessExited] event is raised when the collection of WebView2 Runtime + ///processes for the browser process of this environment terminate due to browser process failure + ///or normal shutdown (for example, when all associated WebViews are closed), + ///after all resources have been released (including the user data folder). + ///To learn about what these processes are, go to [Process model](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). + /// + ///Multiple app processes can share a browser process by creating their webviews + ///from a [PlatformWebViewEnvironment] with the same user data folder. + ///When the entire collection of WebView2Runtime processes for the browser process exit, + ///all associated [PlatformWebViewEnvironment] objects receive the [onBrowserProcessExited] event. + ///Multiple processes sharing the same browser process need to coordinate their + ///use of the shared user data folder to avoid race conditions and unnecessary waits. + ///For example, one process should not clear the user data folder at the same + ///time that another process recovers from a crash by recreating its WebView controls; + ///one process should not block waiting for the event if other app processes + ///are using the same browser process (the browser process will not exit + ///until those other processes have closed their webviews too). + /// + ///The difference between [onBrowserProcessExited] and [PlatformWebViewCreationParams.onProcessFailed] is that + ///[onBrowserProcessExited] is raised for any browser process exit + ///(expected or unexpected, after all associated processes have exited too), + ///while [PlatformWebViewCreationParams.onProcessFailed] is raised for + ///unexpected process exits of any kind (browser, render, GPU, and all other types), + ///or for main frame render process unresponsiveness. + ///To learn more about the WebView2 Process Model, go to + ///[Process model](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). + /// + ///In the case the browser process crashes, both [onBrowserProcessExited] and + ///[PlatformWebViewCreationParams.onProcessFailed] events are raised, but the order is not guaranteed. + ///These events are intended for different scenarios. + ///It is up to the app to coordinate the handlers so they do not try to perform + ///reliability recovery while also trying to move to a new WebView2 Runtime version + ///or remove the user data folder. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.992.28+ ([Official API - ICoreWebView2Environment5.add_BrowserProcessExited](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment5?view=webview2-1.0.2849.39#add_browserprocessexited)) + ///{@endtemplate} + void Function(BrowserProcessExitedDetail detail)? onBrowserProcessExited; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onProcessInfosChanged} + ///Event fired with a list of all process using same user data folder except for crashpad process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1108.44+ ([Official API - ICoreWebView2Environment8.add_ProcessInfosChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment8?view=webview2-1.0.2849.39#add_processinfoschanged)) + ///{@endtemplate} + void Function(BrowserProcessInfosChangedDetail detail)? onProcessInfosChanged; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} + ///Disposes the WebView Environment reference. + ///{@endtemplate} + Future dispose() { + throw UnimplementedError( + 'dispose is not implemented on the current platform'); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart new file mode 100644 index 000000000..c208d8781 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart @@ -0,0 +1,286 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'platform_webview_environment.dart'; +import '../types/custom_scheme_registration.dart'; +import '../types/enum_method.dart'; +import '../types/environment_channel_search_kind.dart'; +import '../types/environment_release_channels.dart'; +import '../types/environment_scrollbar_style.dart'; + +part 'webview_environment_settings.g.dart'; + +///This class represents all the [PlatformWebViewEnvironment] settings available. +/// +///The [browserExecutableFolder], [userDataFolder] and [additionalBrowserArguments] +///may be overridden by values either specified in environment variables or in the registry. +@SupportedPlatforms(platforms: [WindowsPlatform()]) +@ExchangeableObject(copyMethod: true) +class WebViewEnvironmentSettings_ { + ///Use [browserExecutableFolder] to specify whether WebView2 controls use a fixed + ///or installed version of the WebView2 Runtime that exists on a user machine. + ///To use a fixed version of the WebView2 Runtime, pass the folder path that contains + ///the fixed version of the WebView2 Runtime to [browserExecutableFolder]. + ///BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. + ///To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user machines, + ///pass a `null` or empty string to [browserExecutableFolder]. + ///In this scenario, the API tries to find a compatible version of the WebView2 Runtime + ///that is installed on the user machine (first at the machine level, and then per user) using the selected channel preference. + ///The path of fixed version of the WebView2 Runtime should not contain `\Edge\Application\`. + ///When such a path is used, the API fails with `HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)`. + /// + ///The default channel search order is the WebView2 Runtime, Beta, Dev, and Canary. + ///When an override `WEBVIEW2_RELEASE_CHANNEL_PREFERENCE` environment variable or + ///applicable `releaseChannelPreference` registry value is set to `1`, the channel search order is reversed. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: + 'CreateCoreWebView2EnvironmentWithOptions.browserExecutableFolder', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions') + ]) + final String? browserExecutableFolder; + + ///You may specify the [userDataFolder] to change the default user data folder location for WebView2. + ///The path is either an absolute file path or a relative file path that is interpreted as relative + ///to the compiled code for the current process. + ///For UWP apps, the default user data folder is the app data folder for the package. + ///For non-UWP apps, the default user data (`{Executable File Name}.WebView2`) folder + ///is created in the same directory next to the compiled code for the app. + ///WebView2 creation fails if the compiled code is running in a directory in which the + ///process does not have permission to create a new directory. + ///The app is responsible to clean up the associated user data folder when it is done. + /// + ///**NOTE**: As a browser process may be shared among WebViews, + ///WebView creation fails with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` if the specified + ///options does not match the options of the WebViews that are currently + ///running in the shared browser process. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: 'CreateCoreWebView2EnvironmentWithOptions.userDataFolder', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions') + ]) + final String? userDataFolder; + + ///If there are multiple switches, there should be a space in between them. + ///The one exception is if multiple features are being enabled/disabled for a single switch, + ///in which case the features should be comma-seperated. + ///Example: `"--disable-features=feature1,feature2 --some-other-switch --do-something"` + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: + 'ICoreWebView2EnvironmentOptions.put_AdditionalBrowserArguments', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_additionalbrowserarguments') + ]) + final String? additionalBrowserArguments; + + ///This property is used to enable single sign on with Azure Active Directory (AAD) + ///and personal Microsoft Account (MSA) resources inside WebView. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: + 'ICoreWebView2EnvironmentOptions.put_AllowSingleSignOnUsingOSPrimaryAccount', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_allowsinglesignonusingosprimaryaccount') + ]) + final bool? allowSingleSignOnUsingOSPrimaryAccount; + + ///The default display language for WebView. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: 'ICoreWebView2EnvironmentOptions.put_Language', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_language') + ]) + final String? language; + + ///Specifies the version of the WebView2 Runtime binaries required to be compatible with your app. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: + 'ICoreWebView2EnvironmentOptions.put_TargetCompatibleBrowserVersion', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_targetcompatiblebrowserversion') + ]) + final String? targetCompatibleBrowserVersion; + + ///Set the array of custom scheme registrations to be used. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1587.40', + apiName: + 'ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations') + ]) + final List? customSchemeRegistrations; + + ///Whether other processes can create WebView2 from WebView2Environment created + ///with the same user data folder and therefore sharing the same WebView browser process instance. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1185.39', + apiName: + 'ICoreWebView2EnvironmentOptions2.put_ExclusiveUserDataFolderAccess', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions2?view=webview2-1.0.2849.39#put_exclusiveuserdatafolderaccess') + ]) + final bool? exclusiveUserDataFolderAccess; + + ///When IsCustomCrashReportingEnabled is set to `true`, + ///Windows won't send crash data to Microsoft endpoint. + /// + ///The default value is `false`. + ///In this case, WebView will respect OS consent. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1518.46', + apiName: + 'ICoreWebView2EnvironmentOptions3.put_IsCustomCrashReportingEnabled', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions3?view=webview2-1.0.2849.39#put_iscustomcrashreportingenabled') + ]) + final bool? isCustomCrashReportingEnabled; + + ///This property is used to enable/disable tracking prevention feature in WebView2. + /// + ///This property enable/disable tracking prevention for all the WebView2's created in the same environment. + ///By default this feature is enabled to block potentially harmful trackers and trackers from sites that aren't visited before. + /// + ///You can set this property to `false` to disable the tracking prevention feature if the app + ///only renders content in the WebView2 that is known to be safe. + ///Disabling this feature when creating environment also improves runtime performance by skipping related code. + /// + ///You shouldn't disable this property if WebView2 is being used as a "full browser" + ///with arbitrary navigation and should protect end user privacy. + /// + ///Tracking prevention protects users from online tracking by restricting the ability + ///of trackers to access browser-based storage as well as the network. + ///See [Tracking prevention](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/tracking-prevention). + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1661.34', + apiName: + 'ICoreWebView2EnvironmentOptions5.put_EnableTrackingPrevention', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions5?view=webview2-1.0.2849.39#put_enabletrackingprevention') + ]) + final bool? enableTrackingPrevention; + + ///When this property is set to `true` new extensions can be added to user profile and used. + /// + ///[areBrowserExtensionsEnabled] is default to be `false`, in this case, new extensions can't be installed, + ///and already installed extension won't be available to use in user profile. + ///If connecting to an already running environment with a different value for + ///[areBrowserExtensionsEnabled] property, it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2210.55', + apiName: + 'ICoreWebView2EnvironmentOptions6.put_AreBrowserExtensionsEnabled', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions6?view=webview2-1.0.2849.39#put_arebrowserextensionsenabled') + ]) + final bool? areBrowserExtensionsEnabled; + + ///This property is [EnvironmentChannelSearchKind.MOST_STABLE] by default; + ///environment creation searches for a release channel on the machine from + ///most to least stable using the first channel found. + /// + ///The default search order is: WebView2 Runtime -> Beta -> Dev -> Canary. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so that environment creation searches for a channel from least to most stable. + ///If [releaseChannels] has been provided, the loader will only search for channels in the set. + ///See [EnvironmentReleaseChannels] for more details on channels. + /// + ///This property can be overridden by the corresponding registry key [channelSearchKind] + ///or the environment variable `WEBVIEW2_CHANNEL_SEARCH_KIND`. + ///Set the value to `1` to set the search kind to [EnvironmentChannelSearchKind.LEAST_STABLE]. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2478.35', + apiName: 'ICoreWebView2EnvironmentOptions7.put_ChannelSearchKind', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_channelsearchkind') + ]) + final EnvironmentChannelSearchKind_? channelSearchKind; + + ///Sets the [releaseChannels], which is a mask of one or more [EnvironmentReleaseChannels] + ///indicating which channels environment creation should search for. + /// + ///OR operation(s) can be applied to multiple [EnvironmentReleaseChannels] to create a mask. + ///The default value is a a mask of all the channels. + ///By default, environment creation searches for channels from most to least stable, + ///using the first channel found on the device. + ///When [releaseChannels] is provided, environment creation will only + ///search for the channels specified in the set. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so environment creation searches for least stable build first. + ///See [EnvironmentReleaseChannels] for descriptions of each channel. + /// + ///The [PlatformWebViewEnvironment] creation will fails with `HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)` + ///if environment creation is unable to find any channel from the EnvironmentReleaseChannels installed on the device. + ///Use [PlatformWebViewEnvironment.getAvailableVersion] to verify which channel is used when this option is set. + /// + ///Examples: + /// + ///| ReleaseChannels | Channel Search Kind: Most Stable (default) | Channel Search Kind: Least Stable | + ///|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|--------------------------------------------| + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta | Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.DEV] | [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Dev -> Canary | Canary -> Dev -> Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | Canary | Canary | + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Canary | Canary -> Beta -> WebView2 Runtime | + /// + ///If both [browserExecutableFolder] and [releaseChannels] are provided, the [browserExecutableFolder] takes precedence, + ///regardless of whether or not the channel of [browserExecutableFolder] is included in the [releaseChannels]. + ///[releaseChannels] can be overridden by the corresponding registry override EnvironmentReleaseChannels or the environment + ///variable `WEBVIEW2_RELEASE_CHANNELS`. Set the value to a comma-separated string of integers, which map to + ///the following release channel values: Stable (0), Beta (1), Dev (2), and Canary (3). + ///For example, the values "0,2" and "2,0" indicate that environment creation should only search for + ///Dev channel and the WebView2 Runtime, using the order indicated by [channelSearchKind]. + ///[PlatformWebViewEnvironment] creation attempts to interpret each integer and treats any invalid entry as Stable channel. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2478.35', + apiName: 'ICoreWebView2EnvironmentOptions7.put_ReleaseChannels', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_releasechannels') + ]) + final EnvironmentReleaseChannels_? releaseChannels; + + ///The ScrollBar style being set on the WebView2 Environment. + /// + ///The default value is [EnvironmentScrollbarStyle.DEFAULT] which specifies the default browser ScrollBar style. + ///The `color-scheme` CSS property needs to be set on the corresponding + ///page to allow ScrollBar to follow light or dark theme. + ///Please see [color-scheme](https://developer.mozilla.org/docs/Web/CSS/color-scheme#declaring_color_scheme_preferences) for how `color-scheme` can be set. + ///CSS styles that modify the ScrollBar applied on top of native ScrollBar styling that is selected with [scrollbarStyle]. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2535.41', + apiName: 'ICoreWebView2EnvironmentOptions8.put_ScrollBarStyle', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions8?view=webview2-1.0.2849.39#put_scrollbarstyle') + ]) + final EnvironmentScrollbarStyle_? scrollbarStyle; + + WebViewEnvironmentSettings_({ + this.browserExecutableFolder, + this.userDataFolder, + this.additionalBrowserArguments, + this.allowSingleSignOnUsingOSPrimaryAccount, + this.language, + this.targetCompatibleBrowserVersion, + this.customSchemeRegistrations, + this.exclusiveUserDataFolderAccess, + this.isCustomCrashReportingEnabled, + this.enableTrackingPrevention, + this.areBrowserExtensionsEnabled, + this.channelSearchKind, + this.releaseChannels, + this.scrollbarStyle, + }); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart new file mode 100644 index 000000000..8b192f994 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart @@ -0,0 +1,330 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'webview_environment_settings.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///This class represents all the [PlatformWebViewEnvironment] settings available. +/// +///The [browserExecutableFolder], [userDataFolder] and [additionalBrowserArguments] +///may be overridden by values either specified in environment variables or in the registry. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +class WebViewEnvironmentSettings { + ///If there are multiple switches, there should be a space in between them. + ///The one exception is if multiple features are being enabled/disabled for a single switch, + ///in which case the features should be comma-seperated. + ///Example: `"--disable-features=feature1,feature2 --some-other-switch --do-something"` + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_AdditionalBrowserArguments](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_additionalbrowserarguments)) + final String? additionalBrowserArguments; + + ///This property is used to enable single sign on with Azure Active Directory (AAD) + ///and personal Microsoft Account (MSA) resources inside WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_AllowSingleSignOnUsingOSPrimaryAccount](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_allowsinglesignonusingosprimaryaccount)) + final bool? allowSingleSignOnUsingOSPrimaryAccount; + + ///When this property is set to `true` new extensions can be added to user profile and used. + /// + ///[areBrowserExtensionsEnabled] is default to be `false`, in this case, new extensions can't be installed, + ///and already installed extension won't be available to use in user profile. + ///If connecting to an already running environment with a different value for + ///[areBrowserExtensionsEnabled] property, it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2210.55+ ([Official API - ICoreWebView2EnvironmentOptions6.put_AreBrowserExtensionsEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions6?view=webview2-1.0.2849.39#put_arebrowserextensionsenabled)) + final bool? areBrowserExtensionsEnabled; + + ///Use [browserExecutableFolder] to specify whether WebView2 controls use a fixed + ///or installed version of the WebView2 Runtime that exists on a user machine. + ///To use a fixed version of the WebView2 Runtime, pass the folder path that contains + ///the fixed version of the WebView2 Runtime to [browserExecutableFolder]. + ///BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. + ///To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user machines, + ///pass a `null` or empty string to [browserExecutableFolder]. + ///In this scenario, the API tries to find a compatible version of the WebView2 Runtime + ///that is installed on the user machine (first at the machine level, and then per user) using the selected channel preference. + ///The path of fixed version of the WebView2 Runtime should not contain `\Edge\Application\`. + ///When such a path is used, the API fails with `HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)`. + /// + ///The default channel search order is the WebView2 Runtime, Beta, Dev, and Canary. + ///When an override `WEBVIEW2_RELEASE_CHANNEL_PREFERENCE` environment variable or + ///applicable `releaseChannelPreference` registry value is set to `1`, the channel search order is reversed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions.browserExecutableFolder](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + final String? browserExecutableFolder; + + ///This property is [EnvironmentChannelSearchKind.MOST_STABLE] by default; + ///environment creation searches for a release channel on the machine from + ///most to least stable using the first channel found. + /// + ///The default search order is: WebView2 Runtime -> Beta -> Dev -> Canary. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so that environment creation searches for a channel from least to most stable. + ///If [releaseChannels] has been provided, the loader will only search for channels in the set. + ///See [EnvironmentReleaseChannels] for more details on channels. + /// + ///This property can be overridden by the corresponding registry key [channelSearchKind] + ///or the environment variable `WEBVIEW2_CHANNEL_SEARCH_KIND`. + ///Set the value to `1` to set the search kind to [EnvironmentChannelSearchKind.LEAST_STABLE]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2478.35+ ([Official API - ICoreWebView2EnvironmentOptions7.put_ChannelSearchKind](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_channelsearchkind)) + final EnvironmentChannelSearchKind? channelSearchKind; + + ///Set the array of custom scheme registrations to be used. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1587.40+ ([Official API - ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations)) + final List? customSchemeRegistrations; + + ///This property is used to enable/disable tracking prevention feature in WebView2. + /// + ///This property enable/disable tracking prevention for all the WebView2's created in the same environment. + ///By default this feature is enabled to block potentially harmful trackers and trackers from sites that aren't visited before. + /// + ///You can set this property to `false` to disable the tracking prevention feature if the app + ///only renders content in the WebView2 that is known to be safe. + ///Disabling this feature when creating environment also improves runtime performance by skipping related code. + /// + ///You shouldn't disable this property if WebView2 is being used as a "full browser" + ///with arbitrary navigation and should protect end user privacy. + /// + ///Tracking prevention protects users from online tracking by restricting the ability + ///of trackers to access browser-based storage as well as the network. + ///See [Tracking prevention](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/tracking-prevention). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1661.34+ ([Official API - ICoreWebView2EnvironmentOptions5.put_EnableTrackingPrevention](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions5?view=webview2-1.0.2849.39#put_enabletrackingprevention)) + final bool? enableTrackingPrevention; + + ///Whether other processes can create WebView2 from WebView2Environment created + ///with the same user data folder and therefore sharing the same WebView browser process instance. + /// + ///The default value is `false`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1185.39+ ([Official API - ICoreWebView2EnvironmentOptions2.put_ExclusiveUserDataFolderAccess](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions2?view=webview2-1.0.2849.39#put_exclusiveuserdatafolderaccess)) + final bool? exclusiveUserDataFolderAccess; + + ///When IsCustomCrashReportingEnabled is set to `true`, + ///Windows won't send crash data to Microsoft endpoint. + /// + ///The default value is `false`. + ///In this case, WebView will respect OS consent. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1518.46+ ([Official API - ICoreWebView2EnvironmentOptions3.put_IsCustomCrashReportingEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions3?view=webview2-1.0.2849.39#put_iscustomcrashreportingenabled)) + final bool? isCustomCrashReportingEnabled; + + ///The default display language for WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_Language](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_language)) + final String? language; + + ///Sets the [releaseChannels], which is a mask of one or more [EnvironmentReleaseChannels] + ///indicating which channels environment creation should search for. + /// + ///OR operation(s) can be applied to multiple [EnvironmentReleaseChannels] to create a mask. + ///The default value is a a mask of all the channels. + ///By default, environment creation searches for channels from most to least stable, + ///using the first channel found on the device. + ///When [releaseChannels] is provided, environment creation will only + ///search for the channels specified in the set. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so environment creation searches for least stable build first. + ///See [EnvironmentReleaseChannels] for descriptions of each channel. + /// + ///The [PlatformWebViewEnvironment] creation will fails with `HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)` + ///if environment creation is unable to find any channel from the EnvironmentReleaseChannels installed on the device. + ///Use [PlatformWebViewEnvironment.getAvailableVersion] to verify which channel is used when this option is set. + /// + ///Examples: + /// + ///| ReleaseChannels | Channel Search Kind: Most Stable (default) | Channel Search Kind: Least Stable | + ///|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|--------------------------------------------| + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta | Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.DEV] | [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Dev -> Canary | Canary -> Dev -> Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | Canary | Canary | + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Canary | Canary -> Beta -> WebView2 Runtime | + /// + ///If both [browserExecutableFolder] and [releaseChannels] are provided, the [browserExecutableFolder] takes precedence, + ///regardless of whether or not the channel of [browserExecutableFolder] is included in the [releaseChannels]. + ///[releaseChannels] can be overridden by the corresponding registry override EnvironmentReleaseChannels or the environment + ///variable `WEBVIEW2_RELEASE_CHANNELS`. Set the value to a comma-separated string of integers, which map to + ///the following release channel values: Stable (0), Beta (1), Dev (2), and Canary (3). + ///For example, the values "0,2" and "2,0" indicate that environment creation should only search for + ///Dev channel and the WebView2 Runtime, using the order indicated by [channelSearchKind]. + ///[PlatformWebViewEnvironment] creation attempts to interpret each integer and treats any invalid entry as Stable channel. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2478.35+ ([Official API - ICoreWebView2EnvironmentOptions7.put_ReleaseChannels](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_releasechannels)) + final EnvironmentReleaseChannels? releaseChannels; + + ///The ScrollBar style being set on the WebView2 Environment. + /// + ///The default value is [EnvironmentScrollbarStyle.DEFAULT] which specifies the default browser ScrollBar style. + ///The `color-scheme` CSS property needs to be set on the corresponding + ///page to allow ScrollBar to follow light or dark theme. + ///Please see [color-scheme](https://developer.mozilla.org/docs/Web/CSS/color-scheme#declaring_color_scheme_preferences) for how `color-scheme` can be set. + ///CSS styles that modify the ScrollBar applied on top of native ScrollBar styling that is selected with [scrollbarStyle]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2535.41+ ([Official API - ICoreWebView2EnvironmentOptions8.put_ScrollBarStyle](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions8?view=webview2-1.0.2849.39#put_scrollbarstyle)) + final EnvironmentScrollbarStyle? scrollbarStyle; + + ///Specifies the version of the WebView2 Runtime binaries required to be compatible with your app. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_TargetCompatibleBrowserVersion](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_targetcompatiblebrowserversion)) + final String? targetCompatibleBrowserVersion; + + ///You may specify the [userDataFolder] to change the default user data folder location for WebView2. + ///The path is either an absolute file path or a relative file path that is interpreted as relative + ///to the compiled code for the current process. + ///For UWP apps, the default user data folder is the app data folder for the package. + ///For non-UWP apps, the default user data (`{Executable File Name}.WebView2`) folder + ///is created in the same directory next to the compiled code for the app. + ///WebView2 creation fails if the compiled code is running in a directory in which the + ///process does not have permission to create a new directory. + ///The app is responsible to clean up the associated user data folder when it is done. + /// + ///**NOTE**: As a browser process may be shared among WebViews, + ///WebView creation fails with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` if the specified + ///options does not match the options of the WebViews that are currently + ///running in the shared browser process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions.userDataFolder](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + final String? userDataFolder; + + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + WebViewEnvironmentSettings( + {this.additionalBrowserArguments, + this.allowSingleSignOnUsingOSPrimaryAccount, + this.areBrowserExtensionsEnabled, + this.browserExecutableFolder, + this.channelSearchKind, + this.customSchemeRegistrations, + this.enableTrackingPrevention, + this.exclusiveUserDataFolderAccess, + this.isCustomCrashReportingEnabled, + this.language, + this.releaseChannels, + this.scrollbarStyle, + this.targetCompatibleBrowserVersion, + this.userDataFolder}); + + ///Gets a possible [WebViewEnvironmentSettings] instance from a [Map] value. + static WebViewEnvironmentSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = WebViewEnvironmentSettings( + additionalBrowserArguments: map['additionalBrowserArguments'], + allowSingleSignOnUsingOSPrimaryAccount: + map['allowSingleSignOnUsingOSPrimaryAccount'], + areBrowserExtensionsEnabled: map['areBrowserExtensionsEnabled'], + browserExecutableFolder: map['browserExecutableFolder'], + channelSearchKind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => EnvironmentChannelSearchKind.fromNativeValue( + map['channelSearchKind']), + EnumMethod.value => + EnvironmentChannelSearchKind.fromValue(map['channelSearchKind']), + EnumMethod.name => + EnvironmentChannelSearchKind.byName(map['channelSearchKind']) + }, + customSchemeRegistrations: map['customSchemeRegistrations'] != null + ? List.from(map['customSchemeRegistrations'] + .map((e) => CustomSchemeRegistration.fromMap( + e?.cast(), + enumMethod: enumMethod)!)) + : null, + enableTrackingPrevention: map['enableTrackingPrevention'], + exclusiveUserDataFolderAccess: map['exclusiveUserDataFolderAccess'], + isCustomCrashReportingEnabled: map['isCustomCrashReportingEnabled'], + language: map['language'], + releaseChannels: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + EnvironmentReleaseChannels.fromNativeValue(map['releaseChannels']), + EnumMethod.value => + EnvironmentReleaseChannels.fromValue(map['releaseChannels']), + EnumMethod.name => + EnvironmentReleaseChannels.byName(map['releaseChannels']) + }, + scrollbarStyle: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + EnvironmentScrollbarStyle.fromNativeValue(map['scrollbarStyle']), + EnumMethod.value => + EnvironmentScrollbarStyle.fromValue(map['scrollbarStyle']), + EnumMethod.name => + EnvironmentScrollbarStyle.byName(map['scrollbarStyle']) + }, + targetCompatibleBrowserVersion: map['targetCompatibleBrowserVersion'], + userDataFolder: map['userDataFolder'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "additionalBrowserArguments": additionalBrowserArguments, + "allowSingleSignOnUsingOSPrimaryAccount": + allowSingleSignOnUsingOSPrimaryAccount, + "areBrowserExtensionsEnabled": areBrowserExtensionsEnabled, + "browserExecutableFolder": browserExecutableFolder, + "channelSearchKind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => channelSearchKind?.toNativeValue(), + EnumMethod.value => channelSearchKind?.toValue(), + EnumMethod.name => channelSearchKind?.name() + }, + "customSchemeRegistrations": customSchemeRegistrations + ?.map((e) => e.toMap(enumMethod: enumMethod)) + .toList(), + "enableTrackingPrevention": enableTrackingPrevention, + "exclusiveUserDataFolderAccess": exclusiveUserDataFolderAccess, + "isCustomCrashReportingEnabled": isCustomCrashReportingEnabled, + "language": language, + "releaseChannels": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => releaseChannels?.toNativeValue(), + EnumMethod.value => releaseChannels?.toValue(), + EnumMethod.name => releaseChannels?.name() + }, + "scrollbarStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => scrollbarStyle?.toNativeValue(), + EnumMethod.value => scrollbarStyle?.toValue(), + EnumMethod.name => scrollbarStyle?.name() + }, + "targetCompatibleBrowserVersion": targetCompatibleBrowserVersion, + "userDataFolder": userDataFolder, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + ///Returns a copy of WebViewEnvironmentSettings. + WebViewEnvironmentSettings copy() { + return WebViewEnvironmentSettings.fromMap(toMap()) ?? + WebViewEnvironmentSettings(); + } + + @override + String toString() { + return 'WebViewEnvironmentSettings{additionalBrowserArguments: $additionalBrowserArguments, allowSingleSignOnUsingOSPrimaryAccount: $allowSingleSignOnUsingOSPrimaryAccount, areBrowserExtensionsEnabled: $areBrowserExtensionsEnabled, browserExecutableFolder: $browserExecutableFolder, channelSearchKind: $channelSearchKind, customSchemeRegistrations: $customSchemeRegistrations, enableTrackingPrevention: $enableTrackingPrevention, exclusiveUserDataFolderAccess: $exclusiveUserDataFolderAccess, isCustomCrashReportingEnabled: $isCustomCrashReportingEnabled, language: $language, releaseChannels: $releaseChannels, scrollbarStyle: $scrollbarStyle, targetCompatibleBrowserVersion: $targetCompatibleBrowserVersion, userDataFolder: $userDataFolder}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart index 901f7d7d6..03d7d683a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import '../types/enum_method.dart'; import 'asn1_decoder.dart'; import 'asn1_object.dart'; import 'oid.dart'; @@ -86,7 +87,7 @@ class X509Certificate { Uint8List? derDataDecoded; try { - derDataDecoded = Uint8List.fromList(utf8.encode(base64buffer)); + derDataDecoded = Uint8List.fromList(base64Decode(base64buffer)); } catch (e) {} if (derDataDecoded != null) { return derDataDecoded; @@ -402,16 +403,16 @@ class X509Certificate { return description; } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "basicConstraints": basicConstraints?.toMap(), + "basicConstraints": basicConstraints?.toMap(enumMethod: enumMethod), "subjectAlternativeNames": subjectAlternativeNames, "issuerAlternativeNames": issuerAlternativeNames, "extendedKeyUsage": extendedKeyUsage, "issuerDistinguishedName": issuerDistinguishedName, "keyUsage": keyUsage, - "notAfter": notAfter, - "notBefore": notBefore, + "notAfter": notAfter?.millisecondsSinceEpoch, + "notBefore": notBefore?.millisecondsSinceEpoch, "serialNumber": serialNumber, "sigAlgName": sigAlgName, "sigAlgOID": sigAlgOID, @@ -422,12 +423,15 @@ class X509Certificate { "criticalExtensionOIDs": criticalExtensionOIDs, "nonCriticalExtensionOIDs": nonCriticalExtensionOIDs, "encoded": encoded, - "publicKey": publicKey?.toMap(), - "subjectKeyIdentifier": subjectKeyIdentifier?.toMap(), - "authorityKeyIdentifier": authorityKeyIdentifier?.toMap(), - "certificatePolicies": certificatePolicies?.toMap(), - "cRLDistributionPoints": cRLDistributionPoints?.toMap(), - "authorityInfoAccess": authorityInfoAccess?.toMap(), + "publicKey": publicKey?.toMap(enumMethod: enumMethod), + "subjectKeyIdentifier": + subjectKeyIdentifier?.toMap(enumMethod: enumMethod), + "authorityKeyIdentifier": + authorityKeyIdentifier?.toMap(enumMethod: enumMethod), + "certificatePolicies": certificatePolicies?.toMap(enumMethod: enumMethod), + "cRLDistributionPoints": + cRLDistributionPoints?.toMap(enumMethod: enumMethod), + "authorityInfoAccess": authorityInfoAccess?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart index 8618dfde5..982b1e3c5 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import '../types/enum_method.dart'; import 'x509_certificate.dart'; import 'asn1_object.dart'; import 'oid.dart'; @@ -157,7 +158,7 @@ class BasicConstraintExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isCA": isCA, "pathLenConstraint": pathLenConstraint, @@ -187,7 +188,7 @@ class SubjectKeyIdentifierExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "value": value, }; @@ -209,7 +210,7 @@ class AuthorityInfoAccess { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "method": method, "location": location, @@ -251,9 +252,10 @@ class AuthorityInfoAccessExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "infoAccess": infoAccess?.map((e) => e.toMap()).toList(), + "infoAccess": + infoAccess?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -327,7 +329,7 @@ class AuthorityKeyIdentifierExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "keyIdentifier": keyIdentifier, "certificateIssuer": certificateIssuer, @@ -351,7 +353,7 @@ class CertificatePolicyQualifier { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "oid": oid, "value": value, @@ -374,10 +376,11 @@ class CertificatePolicy { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "oid": oid, - "qualifiers": qualifiers?.map((e) => e.toMap()).toList(), + "qualifiers": + qualifiers?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -436,9 +439,10 @@ class CertificatePoliciesExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "policies": policies?.map((e) => e.toMap()).toList(), + "policies": + policies?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -470,7 +474,7 @@ class CRLDistributionPointsExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "crls": crls, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart index c4661e906..30de5b05f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import '../types/enum_method.dart'; import 'asn1_decoder.dart'; import 'asn1_der_encoder.dart'; import 'asn1_object.dart'; @@ -50,7 +51,7 @@ class X509PublicKey { return null; } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "algOid": algOid, "algName": algName, diff --git a/flutter_inappwebview_platform_interface/pubspec.yaml b/flutter_inappwebview_platform_interface/pubspec.yaml index bbb0f0a94..451ca6807 100644 --- a/flutter_inappwebview_platform_interface/pubspec.yaml +++ b/flutter_inappwebview_platform_interface/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview_platform_interface description: A common platform interface for the flutter_inappwebview plugin. -version: 1.0.10 +version: 1.4.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_platform_interface issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,20 +14,21 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter - flutter_inappwebview_internal_annotations: ^1.1.1 - plugin_platform_interface: ^2.1.6 + flutter_inappwebview_internal_annotations: ^1.2.0 + # path: ../dev_packages/flutter_inappwebview_internal_annotations + plugin_platform_interface: ^2.1.8 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 - build_runner: ^2.4.0 + flutter_lints: ^4.0.0 + build_runner: ^2.4.12 generators: path: ../dev_packages/generators diff --git a/flutter_inappwebview_web/CHANGELOG.md b/flutter_inappwebview_web/CHANGELOG.md index 39d6b1c4c..5a2ce4d00 100644 --- a/flutter_inappwebview_web/CHANGELOG.md +++ b/flutter_inappwebview_web/CHANGELOG.md @@ -1,3 +1,38 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Merged "[web] support iframe role and aria-hidden attributes" [2293](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2293) (thanks to [p-mazhnik](https://github.com/p-mazhnik)) +- Fixed 'Type 'int' is not a subtype of type 'JSValue' in type cast' when compiling/running using WASM + +## 1.1.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.3.0 + +## 1.1.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.2.0 + +## 1.1.0+2 + +- Updated flutter_inappwebview_platform_interface version + +## 1.1.0+1 + +- Updated pubspec.yaml + +## 1.1.0 + +- Migrates package to `package:web`. +- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. + ## 1.0.8 - Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10` diff --git a/flutter_inappwebview_web/example/pubspec.lock b/flutter_inappwebview_web/example/pubspec.lock index 2a46e0855..28440f2e0 100644 --- a/flutter_inappwebview_web/example/pubspec.lock +++ b/flutter_inappwebview_web/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -79,25 +79,25 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_platform_interface: dependency: transitive description: name: flutter_inappwebview_platform_interface - sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" + sha256: "2c99bf767900ba029d825bc6f494d30169ee83cdaa038d86e85fe70571d0a655" url: "https://pub.dev" source: hosted - version: "1.0.10" + version: "1.4.0-beta.2" flutter_inappwebview_web: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.0.8" + version: "1.2.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -126,78 +126,94 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: dependency: transitive description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" platform: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" process: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -255,10 +271,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -271,26 +287,26 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "14.2.5" web: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.1.0" webdriver: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_inappwebview_web/lib/assets/web/web_support.js b/flutter_inappwebview_web/lib/assets/web/web_support.js index 031222d0b..e3893d874 100644 --- a/flutter_inappwebview_web/lib/assets/web/web_support.js +++ b/flutter_inappwebview_web/lib/assets/web/web_support.js @@ -1,6 +1,12 @@ window.flutter_inappwebview = { webViews: {}, - createFlutterInAppWebView: function(viewId, iframeId) { + /** + * @param viewId {number | string} + * @param iframe {HTMLIFrameElement} + * @param iframeContainer {HTMLDivElement} + */ + createFlutterInAppWebView: function(viewId, iframe, iframeContainer) { + const iframeId = iframe.id; var webView = { viewId: viewId, iframeId: iframeId, @@ -19,8 +25,6 @@ window.flutter_inappwebview = { }, prepare: function(settings) { webView.settings = settings; - var iframe = document.getElementById(iframeId); - var iframeContainer = document.getElementById(iframeId + '-container'); document.addEventListener('fullscreenchange', function(event) { // document.fullscreenElement will point to the element that diff --git a/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart index 0ac6a2c89..8fb657ff8 100644 --- a/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart @@ -30,8 +30,10 @@ class WebPlatformHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -148,6 +150,7 @@ class WebPlatformHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -350,7 +353,8 @@ class WebPlatformHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } diff --git a/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart index 89777617f..9d9f5e18f 100755 --- a/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart @@ -33,8 +33,10 @@ class WebPlatformInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -157,6 +159,7 @@ class WebPlatformInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -334,7 +337,8 @@ class WebPlatformInAppWebViewWidget extends PlatformInAppWebViewWidget { settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (_webPlatformParams.onDownloadStartRequest != null && + if ((_webPlatformParams.onDownloadStartRequest != null || + _webPlatformParams.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } diff --git a/flutter_inappwebview_web/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_web/lib/src/web_storage/web_storage.dart index ba400d630..636c66654 100644 --- a/flutter_inappwebview_web/lib/src/web_storage/web_storage.dart +++ b/flutter_inappwebview_web/lib/src/web_storage/web_storage.dart @@ -71,7 +71,7 @@ class WebPlatformStorageCreationParams extends PlatformStorageCreationParams { } ///{@macro flutter_inappwebview_platform_interface.PlatformStorage} -abstract class WebPlatformStorage implements PlatformStorage { +abstract mixin class WebPlatformStorage implements PlatformStorage { @override WebPlatformInAppWebViewController? controller; diff --git a/flutter_inappwebview_web/lib/web/headless_inappwebview_manager.dart b/flutter_inappwebview_web/lib/web/headless_inappwebview_manager.dart index 8a4dc5c94..9d0ab7f14 100644 --- a/flutter_inappwebview_web/lib/web/headless_inappwebview_manager.dart +++ b/flutter_inappwebview_web/lib/web/headless_inappwebview_manager.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web/web.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; diff --git a/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart b/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart index 22fd79f8f..c56832b6f 100644 --- a/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart +++ b/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart @@ -1,20 +1,27 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:developer'; +import 'dart:js_interop'; + import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'dart:html'; -import 'dart:js' as js; -import 'dart:developer'; +import 'package:web/web.dart'; import 'headless_inappwebview_manager.dart'; +import 'js_bridge.dart'; import 'web_platform_manager.dart'; +extension on HTMLIFrameElement { + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/csp + external set csp(String? value); + + external String? get csp; +} + class InAppWebViewWebElement implements Disposable { late dynamic _viewId; late BinaryMessenger _messenger; - late DivElement iframeContainer; - late IFrameElement iframe; + late HTMLDivElement iframeContainer; + late HTMLIFrameElement iframe; late MethodChannel? _channel; InAppWebViewSettings? initialSettings; URLRequest? initialUrlRequest; @@ -23,19 +30,19 @@ class InAppWebViewWebElement implements Disposable { String? headlessWebViewId; InAppWebViewSettings? settings; - late js.JsObject bridgeJsObject; + JSWebView? jsWebView; bool isLoading = false; InAppWebViewWebElement( {required dynamic viewId, required BinaryMessenger messenger}) { this._viewId = viewId; this._messenger = messenger; - iframeContainer = DivElement() + iframeContainer = HTMLDivElement() ..id = 'flutter_inappwebview-$_viewId-container' ..style.height = '100%' ..style.width = '100%' ..style.border = 'none'; - iframe = IFrameElement() + iframe = HTMLIFrameElement() ..id = 'flutter_inappwebview-$_viewId' ..style.height = '100%' ..style.width = '100%' @@ -57,10 +64,10 @@ class InAppWebViewWebElement implements Disposable { } }); - bridgeJsObject = js.JsObject.fromBrowserObject( - js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]); - bridgeJsObject['webViews'][_viewId] = bridgeJsObject - .callMethod("createFlutterInAppWebView", [_viewId, iframe.id]); + jsWebView = flutterInAppWebView?.createFlutterInAppWebView( + _viewId is int ? (_viewId as int).toJS : _viewId.toString().toJS, + iframe, + iframeContainer); } /// Handles method calls over the MethodChannel of this plugin. @@ -202,8 +209,10 @@ class InAppWebViewWebElement implements Disposable { initialData = webView.initialData; initialFile = webView.initialFile; - bridgeJsObject['webViews'][_viewId] = bridgeJsObject - .callMethod("createFlutterInAppWebView", [_viewId, iframe.id]); + jsWebView = flutterInAppWebView?.createFlutterInAppWebView( + _viewId is int ? (_viewId as int).toJS : _viewId.toString().toJS, + iframe, + iframeContainer); } } } @@ -225,6 +234,8 @@ class InAppWebViewWebElement implements Disposable { iframe.referrerPolicy; iframe.name = settings!.iframeName ?? iframe.name; iframe.csp = settings!.iframeCsp ?? iframe.csp; + iframe.role = settings!.iframeRole ?? iframe.role; + iframe.ariaHidden = settings!.iframeAriaHidden ?? iframe.ariaHidden; if (settings!.iframeSandbox != null && settings!.iframeSandbox != Sandbox.ALLOW_ALL) { @@ -239,16 +250,7 @@ class InAppWebViewWebElement implements Disposable { } } - _callMethod("prepare", [js.JsObject.jsify(settings!.toMap())]); - } - - dynamic _callMethod(Object method, [List? args]) { - var webViews = bridgeJsObject['webViews'] as js.JsObject; - if (webViews.hasProperty(_viewId)) { - var webview = bridgeJsObject['webViews'][_viewId] as js.JsObject; - return webview.callMethod(method, args); - } - return null; + jsWebView?.prepare(settings?.toMap().jsify()); } void makeInitialLoad() async { @@ -261,7 +263,7 @@ class InAppWebViewWebElement implements Disposable { } } - Future _makeRequest(URLRequest urlRequest, + Future _makeRequest(URLRequest urlRequest, {bool? withCredentials, String? responseType, String? mimeType, @@ -276,11 +278,10 @@ class InAppWebViewWebElement implements Disposable { onProgress: onProgress); } - String _convertHttpResponseToData(HttpRequest httpRequest) { + String _convertHttpResponseToData(XMLHttpRequest httpRequest) { final String contentType = httpRequest.getResponseHeader('content-type') ?? 'text/html'; - return 'data:$contentType,' + - Uri.encodeComponent(httpRequest.responseText ?? ''); + return 'data:$contentType,' + Uri.encodeComponent(httpRequest.responseText); } String getIFrameId() { @@ -316,31 +317,31 @@ class InAppWebViewWebElement implements Disposable { } Future reload() async { - _callMethod("reload"); + jsWebView?.reload(); } Future goBack() async { - _callMethod("goBack"); + jsWebView?.goBack(); } Future goForward() async { - _callMethod("goForward"); + jsWebView?.goForward(); } Future goBackOrForward({required int steps}) async { - _callMethod("goBackOrForward", [steps]); + jsWebView?.goBackOrForward(steps.toJS); } Future evaluateJavascript({required String source}) async { - return _callMethod("evaluateJavascript", [source]); + return jsWebView?.evaluateJavascript(source.toJS)?.toDart; } Future stopLoading() async { - _callMethod("stopLoading"); + jsWebView?.stopLoading(); } Future getUrl() async { - String? url = _callMethod("getUrl"); + String? url = jsWebView?.getUrl()?.toDart; if (url == null || url.isEmpty || url == 'about:blank') { url = iframe.src; } @@ -348,7 +349,7 @@ class InAppWebViewWebElement implements Disposable { } Future getTitle() async { - return _callMethod("getTitle"); + return jsWebView?.getTitle()?.toDart; } Future postUrl( @@ -361,49 +362,41 @@ class InAppWebViewWebElement implements Disposable { Future injectJavascriptFileFromUrl( {required String urlFile, Map? scriptHtmlTagAttributes}) async { - _callMethod("injectJavascriptFileFromUrl", [ - urlFile, - scriptHtmlTagAttributes != null - ? js.JsObject.jsify(scriptHtmlTagAttributes) - : null - ]); + jsWebView?.injectJavascriptFileFromUrl( + urlFile.toJS, scriptHtmlTagAttributes?.jsify()); } Future injectCSSCode({required String source}) async { - _callMethod("injectCSSCode", [source]); + jsWebView?.injectCSSCode(source.toJS); } Future injectCSSFileFromUrl( {required String urlFile, Map? cssLinkHtmlTagAttributes}) async { - _callMethod("injectCSSFileFromUrl", [ - urlFile, - cssLinkHtmlTagAttributes != null - ? js.JsObject.jsify(cssLinkHtmlTagAttributes) - : null - ]); + jsWebView?.injectCSSFileFromUrl( + urlFile.toJS, cssLinkHtmlTagAttributes?.jsify()); } Future scrollTo( {required int x, required int y, bool animated = false}) async { - _callMethod('scrollTo', [x, y, animated]); + jsWebView?.scrollTo(x.toJS, y.toJS, animated.toJS); } Future scrollBy( {required int x, required int y, bool animated = false}) async { - _callMethod('scrollBy', [x, y, animated]); + jsWebView?.scrollBy(x.toJS, y.toJS, animated.toJS); } Future printCurrentPage() async { - _callMethod('printCurrentPage'); + jsWebView?.printCurrentPage(); } Future getContentHeight() async { - return (_callMethod('getContentHeight') as num?)?.toInt(); + return jsWebView?.getContentHeight()?.toDartInt; } Future getContentWidth() async { - return (_callMethod('getContentWidth') as num?)?.toInt(); + return jsWebView?.getContentWidth()?.toDartInt; } Future getOriginalUrl() async { @@ -411,46 +404,48 @@ class InAppWebViewWebElement implements Disposable { } Future getSelectedText() async { - return _callMethod('getSelectedText'); + final jsPromise = jsWebView?.getSelectedText(); + if (jsPromise != null) { + return jsPromise.toDart.then((value) => value?.toDart); + } + return null; } Future getScrollX() async { - return (_callMethod('getScrollX') as num?)?.toInt(); + return jsWebView?.getScrollX()?.toDartInt; } Future getScrollY() async { - return (_callMethod('getScrollY') as num?)?.toInt(); + return jsWebView?.getScrollY()?.toDartInt; } Future isSecureContext() async { - return _callMethod('isSecureContext'); + return jsWebView?.isSecureContext().toDart ?? false; } Future canScrollVertically() async { - return _callMethod('canScrollVertically'); + return jsWebView?.canScrollVertically().toDart ?? false; } Future canScrollHorizontally() async { - return _callMethod('canScrollHorizontally'); + return jsWebView?.canScrollHorizontally().toDart ?? false; } Set getSandbox() { var sandbox = iframe.sandbox; Set values = Set(); - if (sandbox != null) { - for (int i = 0; i < sandbox.length; i++) { - var token = Sandbox.fromNativeValue(sandbox.item(i)); - if (token != null) { - values.add(token); - } + for (int i = 0; i < sandbox.length; i++) { + var token = Sandbox.fromNativeValue(sandbox.item(i)); + if (token != null) { + values.add(token); } } return values.isEmpty ? Set.from(Sandbox.values) : values; } Size getSize() { - var size = _callMethod("getSize") as js.JsObject; - return Size(size["width"]!.toDouble(), size["height"]!.toDouble()); + var size = jsWebView?.getSize(); + return Size(size!.width!.toDartDouble, size.height!.toDartDouble); } Future setSettings(InAppWebViewSettings newSettings) async { @@ -466,20 +461,27 @@ class InAppWebViewWebElement implements Disposable { } if (settings!.iframeAllow != newSettings.iframeAllow) { - iframe.allow = newSettings.iframeAllow; + iframe.allow = newSettings.iframeAllow ?? ''; } if (settings!.iframeAllowFullscreen != newSettings.iframeAllowFullscreen) { - iframe.allowFullscreen = newSettings.iframeAllowFullscreen; + iframe.allowFullscreen = newSettings.iframeAllowFullscreen ?? false; } if (settings!.iframeReferrerPolicy != newSettings.iframeReferrerPolicy) { - iframe.referrerPolicy = newSettings.iframeReferrerPolicy?.toNativeValue(); + iframe.referrerPolicy = + newSettings.iframeReferrerPolicy?.toNativeValue() ?? ''; } if (settings!.iframeName != newSettings.iframeName) { - iframe.name = newSettings.iframeName; + iframe.name = newSettings.iframeName ?? ''; } if (settings!.iframeCsp != newSettings.iframeCsp) { iframe.csp = newSettings.iframeCsp; } + if (settings!.iframeRole != newSettings.iframeRole) { + iframe.role = newSettings.iframeRole; + } + if (settings!.iframeAriaHidden != newSettings.iframeAriaHidden) { + iframe.ariaHidden = newSettings.iframeAriaHidden; + } if (settings!.iframeSandbox != newSettings.iframeSandbox) { var sandbox = newSettings.iframeSandbox; @@ -495,7 +497,7 @@ class InAppWebViewWebElement implements Disposable { } newSettings.iframeSandbox = sandbox; - _callMethod("setSettings", [js.JsObject.jsify(newSettings.toMap())]); + jsWebView?.setSettings(newSettings.toMap().jsify()); settings = newSettings; } @@ -625,11 +627,5 @@ class InAppWebViewWebElement implements Disposable { if (WebPlatformManager.webViews.containsKey(_viewId)) { WebPlatformManager.webViews.remove(_viewId); } - bridgeJsObject = js.JsObject.fromBrowserObject( - js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]); - var webViews = bridgeJsObject['webViews'] as js.JsObject; - if (webViews.hasProperty(_viewId)) { - webViews.deleteProperty(_viewId); - } } } diff --git a/flutter_inappwebview_web/lib/web/js_bridge.dart b/flutter_inappwebview_web/lib/web/js_bridge.dart new file mode 100644 index 000000000..31b0a05ea --- /dev/null +++ b/flutter_inappwebview_web/lib/web/js_bridge.dart @@ -0,0 +1,54 @@ +import 'dart:js_interop'; +import 'package:web/web.dart'; + +extension type JSSize._(JSObject _) implements JSObject { + external JSNumber? get height; + external JSNumber? get width; +} + +extension type JSWebView._(JSObject _) implements JSObject { + external JSAny get viewId; + external JSString get iframeId; + external HTMLIFrameElement get iframe; + external HTMLDivElement get iframeContainer; + external void prepare(JSAny? settings); + external void setSettings(JSAny? newSettings); + external void reload(); + external void goBack(); + external void goForward(); + external void goBackOrForward(JSNumber steps); + external JSString? evaluateJavascript(JSString source); + external void stopLoading(); + external JSString? getUrl(); + external JSString? getTitle(); + external void injectJavascriptFileFromUrl( + JSString urlFile, JSAny? scriptHtmlTagAttributes); + external void injectCSSCode(JSString source); + external void injectCSSFileFromUrl( + JSString urlFile, JSAny? cssLinkHtmlTagAttributes); + external void scrollTo(JSNumber x, JSNumber y, JSBoolean animated); + external void scrollBy(JSNumber x, JSNumber y, JSBoolean animated); + external void printCurrentPage(); + external JSNumber? getContentHeight(); + external JSNumber? getContentWidth(); + external JSPromise? getSelectedText(); + external JSNumber? getScrollX(); + external JSNumber? getScrollY(); + external JSBoolean isSecureContext(); + external JSBoolean canScrollVertically(); + external JSBoolean canScrollHorizontally(); + external JSSize getSize(); +} + +@JS('window.flutter_inappwebview') +external FlutterInAppWebViewBridge? get flutterInAppWebView; + +extension type FlutterInAppWebViewBridge._(JSObject _) implements JSObject { + external JSObject webViews; + external JSWebView createFlutterInAppWebView( + JSAny viewId, HTMLIFrameElement iframe, HTMLDivElement iframeContainer); + external JSString getCookieExpirationDate(num timestamp); + + /// Allows assigning a function to be callable from `window.flutter_inappwebview.nativeCommunication()` + external JSFunction nativeCommunication; +} diff --git a/flutter_inappwebview_web/lib/web/platform_util.dart b/flutter_inappwebview_web/lib/web/platform_util.dart index 294503c82..710479c1b 100644 --- a/flutter_inappwebview_web/lib/web/platform_util.dart +++ b/flutter_inappwebview_web/lib/web/platform_util.dart @@ -1,10 +1,9 @@ import 'dart:async'; +import 'dart:js_interop'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'dart:js' as js; - -import 'web_platform_manager.dart'; +import 'js_bridge.dart'; class PlatformUtil extends ChannelController { late BinaryMessenger _messenger; @@ -36,9 +35,7 @@ class PlatformUtil extends ChannelController { } String getWebCookieExpirationDate(int timestamp) { - var bridgeJsObject = js.JsObject.fromBrowserObject( - js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]); - return bridgeJsObject.callMethod("getCookieExpirationDate", [timestamp]); + return flutterInAppWebView!.getCookieExpirationDate(timestamp).toDart; } @override diff --git a/flutter_inappwebview_web/lib/web/shims/platform_view_registry.dart b/flutter_inappwebview_web/lib/web/shims/platform_view_registry.dart deleted file mode 100644 index 900a2e0b9..000000000 --- a/flutter_inappwebview_web/lib/web/shims/platform_view_registry.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'platform_view_registry_dart_ui.dart' - if (dart.library.ui_web) 'platform_view_registry_dart_ui_web.dart'; diff --git a/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui.dart b/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui.dart deleted file mode 100644 index 1aaddc014..000000000 --- a/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'dart:html' as html; -import 'dart:ui' as ui; - -class platformViewRegistry { - static bool registerViewFactory( - String viewTypeId, html.Element Function(int viewId) viewFactory) { - // ignore: undefined_prefixed_name - return ui.platformViewRegistry.registerViewFactory(viewTypeId, viewFactory); - } -} diff --git a/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui_web.dart b/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui_web.dart deleted file mode 100644 index 5d069c3dd..000000000 --- a/flutter_inappwebview_web/lib/web/shims/platform_view_registry_dart_ui_web.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:html' as html; -// ignore: uri_does_not_exist -import 'dart:ui_web' as ui_web; - -class platformViewRegistry { - static bool registerViewFactory( - String viewTypeId, html.Element Function(int viewId) viewFactory) { - return ui_web.platformViewRegistry - .registerViewFactory(viewTypeId, viewFactory); - } -} diff --git a/flutter_inappwebview_web/lib/web/web_platform.dart b/flutter_inappwebview_web/lib/web/web_platform.dart index de74ea718..5e51c262d 100644 --- a/flutter_inappwebview_web/lib/web/web_platform.dart +++ b/flutter_inappwebview_web/lib/web/web_platform.dart @@ -1,14 +1,14 @@ import 'dart:async'; +import 'dart:js_interop'; +import 'dart:ui_web' as ui_web; import '../src/inappwebview_platform.dart'; import 'headless_inappwebview_manager.dart'; +import 'js_bridge.dart'; import 'web_platform_manager.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'in_app_web_view_web_element.dart'; import 'platform_util.dart'; -import 'package:js/js.dart'; - -import 'shims/platform_view_registry.dart' show platformViewRegistry; /// Builds an iframe based WebView. /// @@ -16,7 +16,7 @@ import 'shims/platform_view_registry.dart' show platformViewRegistry; class InAppWebViewFlutterPlugin { /// Constructs a new instance of [InAppWebViewFlutterPlugin]. InAppWebViewFlutterPlugin(Registrar registrar) { - platformViewRegistry.registerViewFactory( + ui_web.platformViewRegistry.registerViewFactory( 'com.pichillilorenzo/flutter_inappwebview', (int viewId) { var webView = InAppWebViewWebElement(viewId: viewId, messenger: registrar); @@ -33,21 +33,13 @@ class InAppWebViewFlutterPlugin { final platformUtil = PlatformUtil(messenger: registrar); // ignore: unused_local_variable final headlessManager = HeadlessInAppWebViewManager(messenger: registrar); - _nativeCommunication = allowInterop(_dartNativeCommunication); + flutterInAppWebView?.nativeCommunication = ((String method, JSAny viewId, + [JSArray? args]) => + _dartNativeCommunication(method, viewId, args?.toDart).toJS).toJS; } } -/// Allows assigning a function to be callable from `window.flutter_inappwebview.nativeCommunication()` -@JS('flutter_inappwebview.nativeCommunication') -external set _nativeCommunication( - Future Function(String method, dynamic viewId, [List? args]) f); - -/// Allows calling the assigned function from Dart as well. -@JS() -external Future nativeCommunication(String method, dynamic viewId, - [List? args]); - -Future _dartNativeCommunication(String method, dynamic viewId, +Future _dartNativeCommunication(String method, dynamic viewId, [List? args]) async { if (WebPlatformManager.webViews.containsKey(viewId)) { var webViewHtmlElement = @@ -80,8 +72,9 @@ Future _dartNativeCommunication(String method, dynamic viewId, String url = args[1] ?? 'about:blank'; String? target = args[2]; String? windowFeatures = args[3]; - return await webViewHtmlElement.onCreateWindow( - windowId, url, target, windowFeatures); + return (await webViewHtmlElement.onCreateWindow( + windowId, url, target, windowFeatures)) + ?.toJS; case 'onWindowFocus': webViewHtmlElement.onWindowFocus(); break; @@ -117,4 +110,5 @@ Future _dartNativeCommunication(String method, dynamic viewId, break; } } + return null; } diff --git a/flutter_inappwebview_web/lib/web/web_platform_manager.dart b/flutter_inappwebview_web/lib/web/web_platform_manager.dart index 5f08e528d..13cba5010 100644 --- a/flutter_inappwebview_web/lib/web/web_platform_manager.dart +++ b/flutter_inappwebview_web/lib/web/web_platform_manager.dart @@ -1,4 +1,3 @@ abstract class WebPlatformManager { - static final String BRIDGE_JS_OBJECT_NAME = "flutter_inappwebview"; static final Map webViews = {}; } diff --git a/flutter_inappwebview_web/pubspec.yaml b/flutter_inappwebview_web/pubspec.yaml index 8bfb40893..e11dd9823 100644 --- a/flutter_inappwebview_web/pubspec.yaml +++ b/flutter_inappwebview_web/pubspec.yaml @@ -1,9 +1,11 @@ name: flutter_inappwebview_web description: Web implementation of the flutter_inappwebview plugin. -version: 1.0.8 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_web issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ topics: - html - webview @@ -12,22 +14,23 @@ topics: - browser environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter - js: ^0.6.4 - flutter_inappwebview_platform_interface: ^1.0.10 + web: ^1.0.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 - plugin_platform_interface: ^2.0.2 + flutter_lints: ^4.0.0 + plugin_platform_interface: ^2.1.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter_inappwebview_windows/.gitignore b/flutter_inappwebview_windows/.gitignore new file mode 100644 index 000000000..ac5aa9893 --- /dev/null +++ b/flutter_inappwebview_windows/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/flutter_inappwebview_windows/.metadata b/flutter_inappwebview_windows/.metadata new file mode 100644 index 000000000..f0767e5b8 --- /dev/null +++ b/flutter_inappwebview_windows/.metadata @@ -0,0 +1,30 @@ +# 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: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter_inappwebview_windows/CHANGELOG.md b/flutter_inappwebview_windows/CHANGELOG.md new file mode 100644 index 000000000..9aefe57e4 --- /dev/null +++ b/flutter_inappwebview_windows/CHANGELOG.md @@ -0,0 +1,83 @@ +## 0.7.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Merged "windows: fix WebViewEnvironment dispose crash" [#2433](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2433) (thanks to [GooRingX](https://github.com/GooRingX)) + +## 0.7.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Updated Microsoft.Web.WebView2 SDK version from `1.0.2792.45` to `1.0.2849.39` +- Implemented `disableDefaultErrorPage`, `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `allowsBackForwardNavigationGestures`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled` properties of `InAppWebViewSettings` +- Implemented `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` WebViewEnvironment methods +- Implemented `isInterfaceSupported`, `getZoomScale` InAppWebViewController method +- Implemented `onDownloadStarting`, `onAcceleratorKeyPressed` WebView event +- Implemented `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties of `WebViewEnvironmentSettings` +- Implemented `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` WebViewEnvironment events +- Send mouse leave region event to native view +- Fixed wrong channel name when creating a `WebViewEnvironment` instance +- Fixed "[Windows] Has an overlay on the desktop when the application is minimized" [#2402](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2402) +- Fixed "[Windows] missing implementation of onPermissionRequest event will cause crash when requested by the webpage" [#2404](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2404) +- Fixed "Windows: getCookies return empty list" [#2314](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2314) + +## 0.7.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Updated `scrollMultiplier` default value from 6 to 1 +- Added support for `UserScript.allowedOriginRules` and `UserScript.forMainFrameOnly` parameters +- Implemented `onReceivedHttpAuthRequest`, `onReceivedClientCertRequest`, `onReceivedServerTrustAuthRequest`, `onRenderProcessGone`, `onRenderProcessUnresponsive`, `onWebContentProcessDidTerminate`, `onProcessFailed` WebView events +- Implemented `clearSslPreferences` WebView method +- Fixed `get_optional_fl_map_value` implementation in `utils/flutter.h` +- Fixed "Error in transparentBackground handling in Windows" [#2391](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2391) + +## 0.6.0 + +- Updated code to support multiple flutter windows + +## 0.5.0+2 + +- Fixed `InAppWebViewController.callAsyncJavaScript` not working with JSON objects + +## 0.5.0+1 + +- Fixed `onLoadResourceWithCustomScheme` WebView event called every time + +## 0.5.0 + +- Implemented `shouldInterceptRequest`, `onLoadResourceWithCustomScheme` WebView events +- Updated flutter_inappwebview_platform_interface version to ^1.3.0 + +## 0.4.1 + +- Implemented `incognito` for `InAppWebViewSettings` + +## 0.4.0 + +- Updated `shouldOverrideUrlLoading` implementation using the Chrome DevTools Protocol API Fetch.requestPaused event +- Updated flutter_inappwebview_platform_interface version to ^1.2.0 + +## 0.3.0+1 + +- Removed unwanted debug log + +## 0.3.0 + +- Implemented `pause`, `resume`, `getCertificate` methods for `InAppWebViewController` +- Implemented `onPermissionRequest` WebView event + +## 0.2.0+1 + +- Fixed `InAppWebViewController.evaluateJavascript` not working with JSON objects +- Fixed `InAppWebViewManager::METHOD_CHANNEL_NAME` c++ value +- Fixed `InAppWebViewController.takeScreenshot` to behave consistently with the other platforms + +## 0.2.0 + +- Added support for keeping alive InAppWebView widgets +- Added onProgressChanged, onCreateWindow, onCloseWindow support +- Updated Microsoft.Web.WebView2 SDK version from `1.0.2210.55` to `1.0.2792.45` +- Updated pubspec.yaml +- Fixed `CookieManager.setCookie` + +## 0.1.0 + +- Initial release diff --git a/flutter_inappwebview_windows/LICENSE b/flutter_inappwebview_windows/LICENSE new file mode 100644 index 000000000..6ccd8da42 --- /dev/null +++ b/flutter_inappwebview_windows/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/flutter_inappwebview_windows/README.md b/flutter_inappwebview_windows/README.md new file mode 100644 index 000000000..127910a6b --- /dev/null +++ b/flutter_inappwebview_windows/README.md @@ -0,0 +1,13 @@ +# flutter\_inappwebview\_windows + +The Windows WebView2 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/flutter_inappwebview_windows/analysis_options.yaml b/flutter_inappwebview_windows/analysis_options.yaml new file mode 100644 index 000000000..84964c30b --- /dev/null +++ b/flutter_inappwebview_windows/analysis_options.yaml @@ -0,0 +1,15 @@ +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: + deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore + unnecessary_cast: ignore + unnecessary_import: ignore diff --git a/flutter_inappwebview_windows/example/.gitignore b/flutter_inappwebview_windows/example/.gitignore new file mode 100644 index 000000000..29a3a5017 --- /dev/null +++ b/flutter_inappwebview_windows/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/flutter_inappwebview_windows/example/README.md b/flutter_inappwebview_windows/example/README.md new file mode 100644 index 000000000..a07c91fa9 --- /dev/null +++ b/flutter_inappwebview_windows/example/README.md @@ -0,0 +1,16 @@ +# flutter_inappwebview_windows_example + +Demonstrates how to use the flutter_inappwebview_windows 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/flutter_inappwebview_windows/example/analysis_options.yaml b/flutter_inappwebview_windows/example/analysis_options.yaml new file mode 100644 index 000000000..0d2902135 --- /dev/null +++ b/flutter_inappwebview_windows/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/flutter_inappwebview_windows/example/integration_test/plugin_integration_test.dart b/flutter_inappwebview_windows/example/integration_test/plugin_integration_test.dart new file mode 100644 index 000000000..e69de29bb diff --git a/flutter_inappwebview_windows/example/lib/main.dart b/flutter_inappwebview_windows/example/lib/main.dart new file mode 100644 index 000000000..e69de29bb diff --git a/flutter_inappwebview_windows/example/pubspec.lock b/flutter_inappwebview_windows/example/pubspec.lock new file mode 100644 index 000000000..7366198f1 --- /dev/null +++ b/flutter_inappwebview_windows/example/pubspec.lock @@ -0,0 +1,298 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.3" + flutter_inappwebview_windows: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.7.0-beta.3" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_inappwebview_windows/example/pubspec.yaml b/flutter_inappwebview_windows/example/pubspec.yaml new file mode 100644 index 000000000..fc697f2f2 --- /dev/null +++ b/flutter_inappwebview_windows/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: flutter_inappwebview_windows_example +description: "Demonstrates how to use the flutter_inappwebview_windows 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.2.3 <4.0.0' + +# 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_windows: + # When depending on this package from a real application you should use: + # flutter_inappwebview_windows: ^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: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +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: ^2.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: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # 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/custom-fonts/#from-packages diff --git a/flutter_inappwebview_windows/example/test/widget_test.dart b/flutter_inappwebview_windows/example/test/widget_test.dart new file mode 100644 index 000000000..e69de29bb diff --git a/flutter_inappwebview_windows/example/windows/.gitignore b/flutter_inappwebview_windows/example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter_inappwebview_windows/example/windows/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/CMakeLists.txt new file mode 100644 index 000000000..3241da617 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/CMakeLists.txt @@ -0,0 +1,110 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(flutter_inappwebview_windows_example 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_windows_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + 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() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# 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_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Enable the test target. +set(include_flutter_inappwebview_windows_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +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) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +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. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..903f4899d --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +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. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# 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/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app 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. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..3b4ee9033 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); +} diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.h b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/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 RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake b/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..61c79a21d --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows 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}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +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} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# 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 build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter_inappwebview_windows/example/windows/runner/Runner.rc b/flutter_inappwebview_windows/example/windows/runner/Runner.rc new file mode 100644 index 000000000..1815f6d8e --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.pichillilorenzo" "\0" + VALUE "FileDescription", "flutter_inappwebview_windows_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "flutter_inappwebview_windows_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.pichillilorenzo. All rights reserved." "\0" + VALUE "OriginalFilename", "flutter_inappwebview_windows_example.exe" "\0" + VALUE "ProductName", "flutter_inappwebview_windows_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp b/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..955ee3038 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter_inappwebview_windows/example/windows/runner/flutter_window.h b/flutter_inappwebview_windows/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter_inappwebview_windows/example/windows/runner/main.cpp b/flutter_inappwebview_windows/example/windows/runner/main.cpp new file mode 100644 index 000000000..52028bc4e --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"flutter_inappwebview_windows_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter_inappwebview_windows/example/windows/runner/resource.h b/flutter_inappwebview_windows/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico b/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000..c04e20caf Binary files /dev/null and b/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico differ diff --git a/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest b/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..a42ea7687 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter_inappwebview_windows/example/windows/runner/utils.cpp b/flutter_inappwebview_windows/example/windows/runner/utils.cpp new file mode 100644 index 000000000..b2b08734d --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter_inappwebview_windows/example/windows/runner/utils.h b/flutter_inappwebview_windows/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp b/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..60608d0fe --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter_inappwebview_windows/example/windows/runner/win32_window.h b/flutter_inappwebview_windows/example/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart b/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart new file mode 100644 index 000000000..4cd071c62 --- /dev/null +++ b/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart @@ -0,0 +1,3 @@ +library flutter_inappwebview_windows; + +export 'src/main.dart'; diff --git a/flutter_inappwebview_windows/lib/src/cookie_manager.dart b/flutter_inappwebview_windows/lib/src/cookie_manager.dart new file mode 100644 index 000000000..f77c3bf49 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/cookie_manager.dart @@ -0,0 +1,243 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'webview_environment/webview_environment.dart'; + +/// Object specifying creation parameters for creating a [WindowsCookieManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformCookieManagerCreationParams] for +/// more information. +@immutable +class WindowsCookieManagerCreationParams + extends PlatformCookieManagerCreationParams { + /// Creates a new [WindowsCookieManagerCreationParams] instance. + const WindowsCookieManagerCreationParams({this.webViewEnvironment}); + + /// Creates a [WindowsCookieManagerCreationParams] instance based on [PlatformCookieManagerCreationParams]. + factory WindowsCookieManagerCreationParams.fromPlatformCookieManagerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformCookieManagerCreationParams params) { + return WindowsCookieManagerCreationParams( + webViewEnvironment: + params.webViewEnvironment as WindowsWebViewEnvironment?); + } + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager} +class WindowsCookieManager extends PlatformCookieManager + with ChannelController { + /// Creates a new [WindowsCookieManager]. + WindowsCookieManager(PlatformCookieManagerCreationParams params) + : super.implementation( + params is WindowsCookieManagerCreationParams + ? params + : WindowsCookieManagerCreationParams + .fromPlatformCookieManagerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_cookiemanager'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsCookieManager? _instance; + + ///Gets the [WindowsCookieManager] shared instance. + static WindowsCookieManager instance( + {WindowsWebViewEnvironment? webViewEnvironment}) { + if (webViewEnvironment == null) { + if (_instance == null) { + _instance = _init(); + } + return _instance!; + } else { + return WindowsCookieManager(WindowsCookieManagerCreationParams( + webViewEnvironment: webViewEnvironment)); + } + } + + static WindowsCookieManager _init() { + _instance = WindowsCookieManager(WindowsCookieManagerCreationParams()); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @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 { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + assert(value.isNotEmpty); + assert(path.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('name', () => name); + args.putIfAbsent('value', () => value); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent('expiresDate', () => expiresDate); + args.putIfAbsent('maxAge', () => maxAge); + args.putIfAbsent('isSecure', () => isSecure); + args.putIfAbsent('isHttpOnly', () => isHttpOnly); + args.putIfAbsent('sameSite', () => sameSite?.toNativeValue()); + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); + + return await channel?.invokeMethod('setCookie', args) ?? false; + } + + @override + Future> getCookies( + {required WebUri url, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + + List cookies = []; + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); + List cookieListMap = + await channel?.invokeMethod('getCookies', args) ?? []; + cookieListMap = cookieListMap.cast>(); + + cookieListMap.forEach((cookieMap) { + cookies.add(Cookie( + name: cookieMap["name"], + value: cookieMap["value"], + expiresDate: cookieMap["expiresDate"], + isSessionOnly: cookieMap["isSessionOnly"], + domain: cookieMap["domain"], + sameSite: + HTTPCookieSameSitePolicy.fromNativeValue(cookieMap["sameSite"]), + isSecure: cookieMap["isSecure"], + isHttpOnly: cookieMap["isHttpOnly"], + path: cookieMap["path"])); + }); + return cookies; + } + + @override + Future getCookie( + {required WebUri url, + required String name, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); + List cookies = + await channel?.invokeMethod('getCookies', args) ?? []; + cookies = cookies.cast>(); + for (var i = 0; i < cookies.length; i++) { + cookies[i] = cookies[i].cast(); + if (cookies[i]["name"] == name) + return Cookie( + name: cookies[i]["name"], + value: cookies[i]["value"], + expiresDate: cookies[i]["expiresDate"], + isSessionOnly: cookies[i]["isSessionOnly"], + domain: cookies[i]["domain"], + sameSite: HTTPCookieSameSitePolicy.fromNativeValue( + cookies[i]["sameSite"]), + isSecure: cookies[i]["isSecure"], + isHttpOnly: cookies[i]["isHttpOnly"], + path: cookies[i]["path"]); + } + return null; + } + + @override + Future deleteCookie( + {required WebUri url, + required String name, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('name', () => name); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); + return await channel?.invokeMethod('deleteCookie', args) ?? false; + } + + @override + Future deleteCookies( + {required WebUri url, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); + return await channel?.invokeMethod('deleteCookies', args) ?? false; + } + + @override + Future deleteAllCookies() async { + Map args = {}; + args.putIfAbsent( + 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + return await channel?.invokeMethod('deleteAllCookies', args) ?? false; + } + + @override + void dispose() { + // empty + } +} + +extension InternalCookieManager on WindowsCookieManager { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart b/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart new file mode 100644 index 000000000..a3e59f43d --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart @@ -0,0 +1,125 @@ +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 [WindowsFindInteractionController]. +/// +/// 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 WindowsFindInteractionControllerCreationParams + extends PlatformFindInteractionControllerCreationParams { + /// Creates a new [WindowsFindInteractionControllerCreationParams] instance. + const WindowsFindInteractionControllerCreationParams( + {super.onFindResultReceived}); + + /// Creates a [WindowsFindInteractionControllerCreationParams] instance based on [PlatformFindInteractionControllerCreationParams]. + factory WindowsFindInteractionControllerCreationParams.fromPlatformFindInteractionControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformFindInteractionControllerCreationParams params) { + return WindowsFindInteractionControllerCreationParams( + onFindResultReceived: params.onFindResultReceived); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController} +class WindowsFindInteractionController extends PlatformFindInteractionController + with ChannelController { + /// Constructs a [WindowsFindInteractionController]. + WindowsFindInteractionController( + PlatformFindInteractionControllerCreationParams params) + : super.implementation( + params is WindowsFindInteractionControllerCreationParams + ? params + : WindowsFindInteractionControllerCreationParams + .fromPlatformFindInteractionControllerCreationParams(params), + ); + + _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} + Future findAll({String? find}) async { + Map args = {}; + args.putIfAbsent('find', () => find); + await channel?.invokeMethod('findAll', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findNext} + Future findNext({bool forward = true}) async { + Map args = {}; + args.putIfAbsent('forward', () => forward); + await channel?.invokeMethod('findNext', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.clearMatches} + Future clearMatches() async { + Map args = {}; + await channel?.invokeMethod('clearMatches', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.setSearchText} + Future setSearchText(String? searchText) async { + Map args = {}; + args.putIfAbsent('searchText', () => searchText); + await channel?.invokeMethod('setSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getSearchText} + Future getSearchText() async { + Map args = {}; + return await channel?.invokeMethod('getSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getActiveFindSession} + 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 WindowsFindInteractionController { + void init(dynamic id) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id'); + handler = _handleMethod; + initMethodCallHandler(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/find_interaction/main.dart b/flutter_inappwebview_windows/lib/src/find_interaction/main.dart new file mode 100644 index 000000000..a7adaacf7 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/find_interaction/main.dart @@ -0,0 +1,2 @@ +export 'find_interaction_controller.dart' + hide InternalFindInteractionController; diff --git a/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart b/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart new file mode 100644 index 000000000..38b169e8b --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart @@ -0,0 +1,155 @@ +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 [WindowsHttpAuthCredentialDatabase]. +/// +/// 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 WindowsHttpAuthCredentialDatabaseCreationParams + extends PlatformHttpAuthCredentialDatabaseCreationParams { + /// Creates a new [WindowsHttpAuthCredentialDatabaseCreationParams] instance. + const WindowsHttpAuthCredentialDatabaseCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) : super(); + + /// Creates a [WindowsHttpAuthCredentialDatabaseCreationParams] instance based on [PlatformHttpAuthCredentialDatabaseCreationParams]. + factory WindowsHttpAuthCredentialDatabaseCreationParams.fromPlatformHttpAuthCredentialDatabaseCreationParams( + PlatformHttpAuthCredentialDatabaseCreationParams params) { + return WindowsHttpAuthCredentialDatabaseCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHttpAuthCredentialDatabase} +class WindowsHttpAuthCredentialDatabase + extends PlatformHttpAuthCredentialDatabase with ChannelController { + /// Creates a new [WindowsHttpAuthCredentialDatabase]. + WindowsHttpAuthCredentialDatabase( + PlatformHttpAuthCredentialDatabaseCreationParams params) + : super.implementation( + params is WindowsHttpAuthCredentialDatabaseCreationParams + ? params + : WindowsHttpAuthCredentialDatabaseCreationParams + .fromPlatformHttpAuthCredentialDatabaseCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_credential_database'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsHttpAuthCredentialDatabase? _instance; + + ///Gets the database shared instance. + static WindowsHttpAuthCredentialDatabase instance() { + return (_instance != null) ? _instance! : _init(); + } + + static WindowsHttpAuthCredentialDatabase _init() { + _instance = WindowsHttpAuthCredentialDatabase( + WindowsHttpAuthCredentialDatabaseCreationParams( + const PlatformHttpAuthCredentialDatabaseCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future> + getAllAuthCredentials() async { + Map args = {}; + List allCredentials = + await channel?.invokeMethod('getAllAuthCredentials', args) ?? []; + + List result = []; + + for (Map map in allCredentials) { + var element = URLProtectionSpaceHttpAuthCredentials.fromMap( + map.cast()); + if (element != null) { + result.add(element); + } + } + return result; + } + + @override + Future> getHttpAuthCredentials( + {required URLProtectionSpace protectionSpace}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + List credentialList = + await channel?.invokeMethod('getHttpAuthCredentials', args) ?? []; + List credentials = []; + for (Map map in credentialList) { + var credential = URLCredential.fromMap(map.cast()); + if (credential != null) { + credentials.add(credential); + } + } + return credentials; + } + + @override + Future setHttpAuthCredential( + {required URLProtectionSpace protectionSpace, + required URLCredential credential}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + args.putIfAbsent("username", () => credential.username); + args.putIfAbsent("password", () => credential.password); + await channel?.invokeMethod('setHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredential( + {required URLProtectionSpace protectionSpace, + required URLCredential credential}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + args.putIfAbsent("username", () => credential.username); + args.putIfAbsent("password", () => credential.password); + await channel?.invokeMethod('removeHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredentials( + {required URLProtectionSpace protectionSpace}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + await channel?.invokeMethod('removeHttpAuthCredentials', args); + } + + @override + Future clearAllAuthCredentials() async { + Map args = {}; + await channel?.invokeMethod('clearAllAuthCredentials', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalHttpAuthCredentialDatabase + on WindowsHttpAuthCredentialDatabase { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart new file mode 100644 index 000000000..07f513859 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart @@ -0,0 +1,382 @@ +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 [WindowsInAppBrowser]. +/// +/// 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 WindowsInAppBrowserCreationParams + extends PlatformInAppBrowserCreationParams { + /// Creates a new [WindowsInAppBrowserCreationParams] instance. + WindowsInAppBrowserCreationParams( + {super.contextMenu, + super.pullToRefreshController, + this.findInteractionController, + super.initialUserScripts, + super.windowId, + this.webViewEnvironment}); + + /// Creates a [WindowsInAppBrowserCreationParams] instance based on [PlatformInAppBrowserCreationParams]. + factory WindowsInAppBrowserCreationParams.fromPlatformInAppBrowserCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformInAppBrowserCreationParams params) { + return WindowsInAppBrowserCreationParams( + contextMenu: params.contextMenu, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: params.findInteractionController + as WindowsFindInteractionController?, + initialUserScripts: params.initialUserScripts, + windowId: params.windowId, + webViewEnvironment: + params.webViewEnvironment as WindowsWebViewEnvironment?); + } + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} +class WindowsInAppBrowser extends PlatformInAppBrowser with ChannelController { + @override + final String id = IdGenerator.generate(); + + /// Constructs a [WindowsInAppBrowser]. + WindowsInAppBrowser(PlatformInAppBrowserCreationParams params) + : super.implementation( + params is WindowsInAppBrowserCreationParams + ? params + : WindowsInAppBrowserCreationParams + .fromPlatformInAppBrowserCreationParams(params), + ) { + _contextMenu = params.contextMenu; + } + + static final WindowsInAppBrowser _staticValue = + WindowsInAppBrowser(WindowsInAppBrowserCreationParams()); + + /// Provide static access. + factory WindowsInAppBrowser.static() { + return _staticValue; + } + + WindowsInAppBrowserCreationParams get _windowsParams => + params as WindowsInAppBrowserCreationParams; + + static const MethodChannel _staticChannel = + const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); + + ContextMenu? _contextMenu; + + @override + ContextMenu? get contextMenu => _contextMenu; + + Map _menuItems = HashMap(); + bool _isOpened = false; + WindowsInAppWebViewController? _webViewController; + + @override + WindowsInAppWebViewController? get webViewController { + return _isOpened ? _webViewController : null; + } + + _init() { + channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); + handler = _handleMethod; + initMethodCallHandler(); + + _webViewController = WindowsInAppWebViewController.fromInAppBrowser( + WindowsInAppWebViewControllerCreationParams(id: id), + channel!, + this, + this.initialUserScripts); + _windowsParams.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', () => _windowsParams.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 WindowsInAppBrowser { + void setContextMenu(ContextMenu? contextMenu) { + _contextMenu = contextMenu; + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart b/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart new file mode 100644 index 000000000..e11eb8b18 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart @@ -0,0 +1 @@ +export 'in_app_browser.dart' hide InternalInAppBrowser; diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart new file mode 100644 index 000000000..beb7de707 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart @@ -0,0 +1,4 @@ +import 'package:flutter/services.dart'; + +const IN_APP_WEBVIEW_STATIC_CHANNEL = + MethodChannel('com.pichillilorenzo/flutter_inappwebview_manager'); diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart new file mode 100644 index 000000000..dbffdd7f3 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart @@ -0,0 +1,485 @@ +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 '../platform_util.dart'; +import '_static_channel.dart'; + +const Map _cursors = { + 'none': SystemMouseCursors.none, + 'basic': SystemMouseCursors.basic, + 'click': SystemMouseCursors.click, + 'forbidden': SystemMouseCursors.forbidden, + 'wait': SystemMouseCursors.wait, + 'progress': SystemMouseCursors.progress, + 'contextMenu': SystemMouseCursors.contextMenu, + 'help': SystemMouseCursors.help, + 'text': SystemMouseCursors.text, + 'verticalText': SystemMouseCursors.verticalText, + 'cell': SystemMouseCursors.cell, + 'precise': SystemMouseCursors.precise, + 'move': SystemMouseCursors.move, + 'grab': SystemMouseCursors.grab, + 'grabbing': SystemMouseCursors.grabbing, + 'noDrop': SystemMouseCursors.noDrop, + 'alias': SystemMouseCursors.alias, + 'copy': SystemMouseCursors.copy, + '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 +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +enum PointerButton { none, primary, secondary, tertiary } + +/// Pointer Event kind +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +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(); + } + + /// Limits the number of frames per second to the given value. + Future setFpsLimit([int? maxFps = 0]) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setFpsLimit', maxFps); + } + + /// Sends a Pointer (Touch) update + Future _setPointerUpdate(InAppWebViewPointerEventKind kind, int pointer, + Offset position, double size, double pressure) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerUpdate', + [pointer, kind.index, position.dx, position.dy, size, pressure]); + } + + /// 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}); + } + + /// 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]); + } + + /// 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 surface size to the provided [size]. + Future _setPosition(Offset position, double scaleFactor) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setPosition', [position.dx, position.dy, scaleFactor]); + } +} + +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. + /// This should only be needed to mimic old behavior before high-DPI support + /// was available. + final double? scaleFactor; + + /// The [FilterQuality] used for scaling the texture's contents. + /// Defaults to [FilterQuality.none] as this renders in native resolution + /// unless specifying a [scaleFactor]. + final FilterQuality filterQuality; + + final dynamic creationParams; + + final Function(int id)? onPlatformViewCreated; + + const CustomPlatformView( + {this.creationParams, + this.onPlatformViewCreated, + this.scaleFactor, + this.filterQuality = FilterQuality.none}); + + @override + _CustomPlatformViewState createState() => _CustomPlatformViewState(); +} + +class _CustomPlatformViewState extends State + with PlatformUtilListener { + final GlobalKey _key = GlobalKey(); + final _downButtons = {}; + + PointerDeviceKind _pointerKind = PointerDeviceKind.unknown; + + MouseCursor _cursor = SystemMouseCursors.basic; + + final _controller = CustomPlatformViewController(); + final _focusNode = FocusNode(); + + StreamSubscription? _cursorSubscription; + + late final AppLifecycleListener _listener; + + PlatformUtil _platformUtil = PlatformUtil.instance(); + + @override + void initState() { + super.initState(); + + _platformUtil.addListener(this); + + _controller.initialize( + onPlatformViewCreated: (id) { + widget.onPlatformViewCreated?.call(id); + setState(() {}); + }, + arguments: widget.creationParams); + + _listener = AppLifecycleListener(onStateChange: (state) { + if ([AppLifecycleState.resumed, AppLifecycleState.hidden] + .contains(state)) { + _reportSurfaceSize(); + _reportWidgetPosition(); + } + }); + + // Report initial surface size and widget position + WidgetsBinding.instance.addPostFrameCallback((_) { + _reportSurfaceSize(); + _reportWidgetPosition(); + }); + + _cursorSubscription = _controller._cursor.listen((cursor) { + setState(() { + _cursor = cursor; + }); + }); + } + + @override + void onWindowMove() { + _reportSurfaceSize(); + _reportWidgetPosition(); + } + + @override + Widget build(BuildContext context) { + return Focus( + autofocus: true, + focusNode: _focusNode, + canRequestFocus: true, + debugLabel: "flutter_inappwebview_windows_custom_platform_view", + onFocusChange: (focused) {}, + child: SizedBox.expand(key: _key, child: _buildInner()), + ); + } + + Widget _buildInner() { + return NotificationListener( + onNotification: (notification) { + _reportSurfaceSize(); + _reportWidgetPosition(); + return true; + }, + child: SizeChangedLayoutNotifier( + child: _controller.value.isInitialized + ? Listener( + onPointerHover: (ev) { + // ev.kind is for whatever reason not set to touch + // even on touch input + if (_pointerKind == PointerDeviceKind.touch) { + // Ignoring hover events on touch for now + return; + } + _controller._setCursorPos(ev.localPosition); + }, + onPointerDown: (ev) { + _reportSurfaceSize(); + _reportWidgetPosition(); + + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + Future.delayed(const Duration(milliseconds: 50), () { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }); + } + + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.down, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _getButton(ev.buttons); + _downButtons[ev.pointer] = button; + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.down, button); + }, + onPointerUp: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.up, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.up, button); + } + }, + onPointerCancel: (ev) { + _pointerKind = ev.kind; + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.cancel, button); + } + }, + onPointerMove: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.update, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + } else { + _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())); + } + + 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)); + } + } + + void _reportWidgetPosition() async { + final box = _key.currentContext?.findRenderObject() as RenderBox?; + if (box != null) { + await _controller.ready; + final position = box.localToGlobal(Offset.zero); + unawaited(_controller._setPosition( + position, widget.scaleFactor ?? window.devicePixelRatio)); + } + } + + @override + void dispose() { + super.dispose(); + _platformUtil.removeListener(this); + _cursorSubscription?.cancel(); + _controller.dispose(); + _focusNode.dispose(); + _listener.dispose(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart new file mode 100644 index 000000000..5c3034b76 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart @@ -0,0 +1,462 @@ +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 [WindowsHeadlessInAppWebView]. +/// +/// 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 WindowsHeadlessInAppWebViewCreationParams + extends PlatformHeadlessInAppWebViewCreationParams { + /// Creates a new [WindowsHeadlessInAppWebViewCreationParams] instance. + WindowsHeadlessInAppWebViewCreationParams( + {super.controllerFromPlatform, + super.initialSize, + this.webViewEnvironment, + super.windowId, + 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 [WindowsHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams]. + WindowsHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( + PlatformHeadlessInAppWebViewCreationParams params) + : this( + controllerFromPlatform: params.controllerFromPlatform, + webViewEnvironment: + params.webViewEnvironment as WindowsWebViewEnvironment?, + initialSize: params.initialSize, + windowId: params.windowId, + 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 WindowsFindInteractionController?); + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} +class WindowsHeadlessInAppWebView 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'); + + WindowsInAppWebViewController? _webViewController; + + /// Constructs a [WindowsHeadlessInAppWebView]. + WindowsHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params) + : super.implementation( + params is WindowsHeadlessInAppWebViewCreationParams + ? params + : WindowsHeadlessInAppWebViewCreationParams + .fromPlatformHeadlessInAppWebViewCreationParams(params), + ) { + id = IdGenerator.generate(); + } + + @override + WindowsInAppWebViewController? get webViewController => _webViewController; + + dynamic _controllerFromPlatform; + + WindowsHeadlessInAppWebViewCreationParams get _windowsParams => + params as WindowsHeadlessInAppWebViewCreationParams; + + _init() { + _webViewController = WindowsInAppWebViewController( + WindowsInAppWebViewControllerCreationParams( + id: id, webviewParams: params), + ); + _controllerFromPlatform = + params.controllerFromPlatform?.call(_webViewController!) ?? + _webViewController!; + _windowsParams.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; + } + + Future run() async { + if (_started) { + return; + } + _started = true; + _init(); + + final initialSettings = params.initialSettings ?? InAppWebViewSettings(); + _inferInitialSettings(initialSettings); + + Map settingsMap = + (params.initialSettings != null ? initialSettings.toMap() : null) ?? + params.initialOptions?.toMap() ?? + initialSettings.toMap(); + + Map pullToRefreshSettings = + _windowsParams.pullToRefreshController?.params.settings.toMap() ?? + _windowsParams.pullToRefreshController?.params.options.toMap() ?? + PullToRefreshSettings(enabled: false).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() ?? + [], + 'pullToRefreshSettings': pullToRefreshSettings, + 'initialSize': params.initialSize.toMap(), + 'webViewEnvironmentId': params.webViewEnvironment?.id, + }); + try { + await _sharedChannel.invokeMethod('run', args); + _running = true; + } catch (e) { + _running = false; + _started = false; + throw e; + } + } + + 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(); + 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; + _windowsParams.pullToRefreshController?.dispose(); + _windowsParams.findInteractionController?.dispose(); + } +} + +extension InternalHeadlessInAppWebView on WindowsHeadlessInAppWebView { + Future internalDispose() async { + _started = false; + _running = false; + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart new file mode 100644 index 000000000..d916e4a86 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart @@ -0,0 +1,424 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../webview_environment/webview_environment.dart'; +import 'headless_in_app_webview.dart'; + +import '../find_interaction/find_interaction_controller.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 WindowsInAppWebViewWidgetCreationParams + extends PlatformInAppWebViewWidgetCreationParams { + WindowsInAppWebViewWidgetCreationParams( + {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, + @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}); + + /// Constructs a [WindowsInAppWebViewWidgetCreationParams] using a + /// [PlatformInAppWebViewWidgetCreationParams]. + WindowsInAppWebViewWidgetCreationParams.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 WindowsWebViewEnvironment?, + 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 WindowsFindInteractionController?); + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} +class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget { + /// Constructs a [WindowsInAppWebViewWidget]. + /// + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} + WindowsInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params) + : super.implementation( + params is WindowsInAppWebViewWidgetCreationParams + ? params + : WindowsInAppWebViewWidgetCreationParams + .fromPlatformInAppWebViewWidgetCreationParams(params), + ); + + WindowsInAppWebViewWidgetCreationParams get _windowsParams => + params as WindowsInAppWebViewWidgetCreationParams; + + WindowsInAppWebViewController? _controller; + + WindowsHeadlessInAppWebView? get _windowsHeadlessInAppWebView => + params.headlessWebView as WindowsHeadlessInAppWebView?; + + @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(); + + Map pullToRefreshSettings = + params.pullToRefreshController?.params.settings.toMap() ?? + // ignore: deprecated_member_use_from_same_package + params.pullToRefreshController?.params.options.toMap() ?? + PullToRefreshSettings(enabled: false).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; + _windowsHeadlessInAppWebView?.internalDispose(); + _controller = WindowsInAppWebViewController( + PlatformInAppWebViewControllerCreationParams( + id: viewId, webviewParams: params)); + _windowsParams.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?.getViewId(); + 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/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart new file mode 100644 index 000000000..03e5dc040 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart @@ -0,0 +1,2876 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:core'; +import 'dart:developer' as developer; +import 'dart:io'; + +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 '../print_job/main.dart'; +import '../web_message/main.dart'; +import '../web_storage/web_storage.dart'; +import '_static_channel.dart'; +import 'headless_in_app_webview.dart'; + +/// Object specifying creation parameters for creating a [WindowsInAppWebViewController]. +/// +/// 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 WindowsInAppWebViewControllerCreationParams + extends PlatformInAppWebViewControllerCreationParams { + /// Creates a new [WindowsInAppWebViewControllerCreationParams] instance. + const WindowsInAppWebViewControllerCreationParams( + {required super.id, super.webviewParams}); + + /// Creates a [WindowsInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams]. + factory WindowsInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformInAppWebViewControllerCreationParams params) { + return WindowsInAppWebViewControllerCreationParams( + id: params.id, webviewParams: params.webviewParams); + } +} + +///Controls a WebView, such as an [InAppWebView] widget instance, a [WindowsHeadlessInAppWebView] instance or [WindowsInAppBrowser] WebView instance. +/// +///If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated] +///callback. Instead, if you are using an [WindowsInAppBrowser] instance, you can get it through the [WindowsInAppBrowser.webViewController] attribute. +class WindowsInAppWebViewController 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 _webMessageListenerObjNames = Set(); + Map _injectedScriptsFromURL = {}; + Set _webMessageChannels = Set(); + Set _webMessageListeners = Set(); + Map _devToolsProtocolEventListenerMap = + HashMap(); + + // static map that contains the properties to be saved and restored for keep alive feature + static final Map + _keepAliveMap = {}; + + WindowsInAppBrowser? _inAppBrowser; + + PlatformInAppBrowserEvents? get _inAppBrowserEventHandler => + _inAppBrowser?.eventHandler; + + dynamic _controllerFromPlatform; + + @override + late WindowsWebStorage webStorage; + + WindowsInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params) + : super.implementation(params + is WindowsInAppWebViewControllerCreationParams + ? params + : WindowsInAppWebViewControllerCreationParams + .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 WindowsInAppWebViewController _staticValue = + WindowsInAppWebViewController( + WindowsInAppWebViewControllerCreationParams(id: null)); + + factory WindowsInAppWebViewController.static() { + return _staticValue; + } + + WindowsInAppWebViewController.fromInAppBrowser( + PlatformInAppWebViewControllerCreationParams params, + MethodChannel channel, + WindowsInAppBrowser inAppBrowser, + UnmodifiableListView? initialUserScripts) + : super.implementation( + params is WindowsInAppWebViewControllerCreationParams + ? params + : WindowsInAppWebViewControllerCreationParams + .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 = WindowsWebStorage(WindowsWebStorageCreationParams( + localStorage: WindowsLocalStorage.defaultStorage(controller: this), + sessionStorage: + WindowsSessionStorage.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, + webMessageChannels: _webMessageChannels, + webMessageListeners: _webMessageListeners, + devToolsProtocolEventListenerMap: + _devToolsProtocolEventListenerMap); + } else { + // restore controller properties + _injectedScriptsFromURL = props.injectedScriptsFromURL; + _javaScriptHandlersMap = props.javaScriptHandlersMap; + _userScripts = props.userScripts; + _webMessageListenerObjNames = props.webMessageListenerObjNames; + _webMessageChannels = + props.webMessageChannels as Set; + _webMessageListeners = + props.webMessageListeners as Set; + _devToolsProtocolEventListenerMap = + props.devToolsProtocolEventListenerMap; + } + } + } + } + + _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 "onReceivedError": + if ((webviewParams != null && + (webviewParams!.onReceivedError != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadError != null)) || + _inAppBrowserEventHandler != null) { + WebResourceRequest request = WebResourceRequest.fromMap( + call.arguments["request"].cast())!; + WebResourceError error = WebResourceError.fromMap( + call.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) { + WebResourceRequest request = WebResourceRequest.fromMap( + call.arguments["request"].cast())!; + WebResourceResponse errorResponse = WebResourceResponse.fromMap( + call.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 "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 "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 "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 "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 "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(); + } + } + break; + case "onLoadResourceWithCustomScheme": + if ((webviewParams != null && + (webviewParams!.onLoadResourceWithCustomScheme != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadResourceCustomScheme != null)) || + _inAppBrowserEventHandler != null) { + Map requestMap = + call.arguments["request"].cast(); + WebResourceRequest request = WebResourceRequest.fromMap(requestMap)!; + + if (webviewParams != null) { + if (webviewParams!.onLoadResourceWithCustomScheme != null) + return (await webviewParams!.onLoadResourceWithCustomScheme!( + _controllerFromPlatform, request)) + ?.toMap(); + else { + return (await params + .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 "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); + } + break; + case "onCloseWindow": + if (webviewParams != null && webviewParams!.onCloseWindow != null) + webviewParams!.onCloseWindow!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onCloseWindow(); + 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 "onGeolocationPermissionsShowPrompt": + if ((webviewParams != null && + (webviewParams!.onGeolocationPermissionsShowPrompt != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsShowPrompt != + null)) || + _inAppBrowserEventHandler != null) { + String origin = call.arguments["origin"]; + + if (webviewParams != null) { + if (webviewParams!.onGeolocationPermissionsShowPrompt != null) + return (await webviewParams!.onGeolocationPermissionsShowPrompt!( + _controllerFromPlatform, origin)) + ?.toMap(); + else { + return (await params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .androidOnGeolocationPermissionsShowPrompt!( + _controllerFromPlatform, origin)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onGeolocationPermissionsShowPrompt(origin)) ?? + (await _inAppBrowserEventHandler! + .androidOnGeolocationPermissionsShowPrompt(origin))) + ?.toMap(); + } + } + break; + case "onGeolocationPermissionsHidePrompt": + if (webviewParams != null && + (webviewParams!.onGeolocationPermissionsHidePrompt != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsHidePrompt != + null)) { + if (webviewParams!.onGeolocationPermissionsHidePrompt != null) + webviewParams! + .onGeolocationPermissionsHidePrompt!(_controllerFromPlatform); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsHidePrompt!( + _controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onGeolocationPermissionsHidePrompt(); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnGeolocationPermissionsHidePrompt(); + } + break; + case "shouldInterceptRequest": + if ((webviewParams != null && + (webviewParams!.shouldInterceptRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidShouldInterceptRequest != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + WebResourceRequest request = WebResourceRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.shouldInterceptRequest != null) + return (await webviewParams!.shouldInterceptRequest!( + _controllerFromPlatform, request)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidShouldInterceptRequest!( + _controllerFromPlatform, request)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .shouldInterceptRequest(request)) ?? + (await _inAppBrowserEventHandler! + .androidShouldInterceptRequest(request))) + ?.toMap(); + } + } + break; + case "onRenderProcessUnresponsive": + if ((webviewParams != null && + (webviewParams!.onRenderProcessUnresponsive != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessUnresponsive != + null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessUnresponsive != null) + return (await webviewParams!.onRenderProcessUnresponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnRenderProcessUnresponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onRenderProcessUnresponsive(uri)) ?? + (await _inAppBrowserEventHandler! + .androidOnRenderProcessUnresponsive(uri))) + ?.toNativeValue(); + } + } + break; + case "onRenderProcessResponsive": + if ((webviewParams != null && + (webviewParams!.onRenderProcessResponsive != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessResponsive != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessResponsive != null) + return (await webviewParams!.onRenderProcessResponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnRenderProcessResponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onRenderProcessResponsive(uri)) ?? + (await _inAppBrowserEventHandler! + .androidOnRenderProcessResponsive(uri))) + ?.toNativeValue(); + } + } + break; + case "onRenderProcessGone": + if ((webviewParams != null && + (webviewParams!.onRenderProcessGone != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessGone != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + RenderProcessGoneDetail detail = + RenderProcessGoneDetail.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessGone != null) + webviewParams!.onRenderProcessGone!( + _controllerFromPlatform, detail); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessGone!( + _controllerFromPlatform, detail); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onRenderProcessGone(detail); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.androidOnRenderProcessGone(detail); + } + } + break; + case "onFormResubmission": + if ((webviewParams != null && + (webviewParams!.onFormResubmission != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnFormResubmission != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onFormResubmission != null) + return (await webviewParams!.onFormResubmission!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnFormResubmission!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onFormResubmission(uri)) ?? + // ignore: deprecated_member_use_from_same_package + (await _inAppBrowserEventHandler! + .androidOnFormResubmission(uri))) + ?.toNativeValue(); + } + } + break; + case "onZoomScaleChanged": + if ((webviewParams != null && + // ignore: deprecated_member_use_from_same_package + (webviewParams!.androidOnScaleChanged != null || + webviewParams!.onZoomScaleChanged != null)) || + _inAppBrowserEventHandler != null) { + double oldScale = call.arguments["oldScale"]; + double newScale = call.arguments["newScale"]; + + if (webviewParams != null) { + if (webviewParams!.onZoomScaleChanged != null) + webviewParams!.onZoomScaleChanged!( + _controllerFromPlatform, oldScale, newScale); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnScaleChanged!( + _controllerFromPlatform, oldScale, newScale); + } + } else { + _inAppBrowserEventHandler!.onZoomScaleChanged(oldScale, newScale); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnScaleChanged(oldScale, newScale); + } + } + break; + case "onReceivedIcon": + if ((webviewParams != null && + (webviewParams!.onReceivedIcon != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedIcon != null)) || + _inAppBrowserEventHandler != null) { + Uint8List icon = + Uint8List.fromList(call.arguments["icon"].cast()); + + if (webviewParams != null) { + if (webviewParams!.onReceivedIcon != null) + webviewParams!.onReceivedIcon!(_controllerFromPlatform, icon); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedIcon!( + _controllerFromPlatform, icon); + } + } else { + _inAppBrowserEventHandler!.onReceivedIcon(icon); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.androidOnReceivedIcon(icon); + } + } + break; + case "onReceivedTouchIconUrl": + if ((webviewParams != null && + (webviewParams!.onReceivedTouchIconUrl != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedTouchIconUrl != null)) || + _inAppBrowserEventHandler != null) { + String url = call.arguments["url"]; + bool precomposed = call.arguments["precomposed"]; + WebUri uri = WebUri(url); + + if (webviewParams != null) { + if (webviewParams!.onReceivedTouchIconUrl != null) + webviewParams!.onReceivedTouchIconUrl!( + _controllerFromPlatform, uri, precomposed); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedTouchIconUrl!( + _controllerFromPlatform, uri, precomposed); + } + } else { + _inAppBrowserEventHandler!.onReceivedTouchIconUrl(uri, precomposed); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnReceivedTouchIconUrl(uri, precomposed); + } + } + break; + 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(); + } + break; + 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(); + } + break; + 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(); + } + break; + case "onJsBeforeUnload": + if ((webviewParams != null && + (webviewParams!.onJsBeforeUnload != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnJsBeforeUnload != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + JsBeforeUnloadRequest jsBeforeUnloadRequest = + JsBeforeUnloadRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onJsBeforeUnload != null) + return (await webviewParams!.onJsBeforeUnload!( + _controllerFromPlatform, jsBeforeUnloadRequest)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnJsBeforeUnload!( + _controllerFromPlatform, jsBeforeUnloadRequest)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onJsBeforeUnload(jsBeforeUnloadRequest)) ?? + (await _inAppBrowserEventHandler! + .androidOnJsBeforeUnload(jsBeforeUnloadRequest))) + ?.toMap(); + } + } + break; + case "onSafeBrowsingHit": + if ((webviewParams != null && + (webviewParams!.onSafeBrowsingHit != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnSafeBrowsingHit != null)) || + _inAppBrowserEventHandler != null) { + String url = call.arguments["url"]; + SafeBrowsingThreat? threatType = + SafeBrowsingThreat.fromNativeValue(call.arguments["threatType"]); + WebUri uri = WebUri(url); + + if (webviewParams != null) { + if (webviewParams!.onSafeBrowsingHit != null) + return (await webviewParams!.onSafeBrowsingHit!( + _controllerFromPlatform, uri, threatType)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnSafeBrowsingHit!( + _controllerFromPlatform, uri, threatType)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onSafeBrowsingHit(uri, threatType)) ?? + (await _inAppBrowserEventHandler! + .androidOnSafeBrowsingHit(uri, threatType))) + ?.toMap(); + } + } + break; + case "onReceivedLoginRequest": + if ((webviewParams != null && + (webviewParams!.onReceivedLoginRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedLoginRequest != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + LoginRequest loginRequest = LoginRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onReceivedLoginRequest != null) + webviewParams!.onReceivedLoginRequest!( + _controllerFromPlatform, loginRequest); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedLoginRequest!( + _controllerFromPlatform, loginRequest); + } + } else { + _inAppBrowserEventHandler!.onReceivedLoginRequest(loginRequest); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnReceivedLoginRequest(loginRequest); + } + } + break; + case "onPermissionRequestCanceled": + if ((webviewParams != null && + webviewParams!.onPermissionRequestCanceled != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + PermissionRequest permissionRequest = + PermissionRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onPermissionRequestCanceled != null) + webviewParams!.onPermissionRequestCanceled!( + _controllerFromPlatform, permissionRequest); + else + _inAppBrowserEventHandler! + .onPermissionRequestCanceled(permissionRequest); + } + break; + case "onRequestFocus": + if ((webviewParams != null && webviewParams!.onRequestFocus != null) || + _inAppBrowserEventHandler != null) { + if (webviewParams != null && webviewParams!.onRequestFocus != null) + webviewParams!.onRequestFocus!(_controllerFromPlatform); + else + _inAppBrowserEventHandler!.onRequestFocus(); + } + break; + case "onReceivedHttpAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedHttpAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + + 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(); + } + break; + case "onReceivedServerTrustAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedServerTrustAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + + 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(); + } + break; + case "onReceivedClientCertRequest": + if ((webviewParams != null && + webviewParams!.onReceivedClientCertRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + + arguments['mutuallyTrustedCertificates'] = + (arguments['mutuallyTrustedCertificates'] as List) + .cast>() + .map((c) { + c['x509Certificate'] = utf8.encode(c['x509Certificate']); + return c; + }).toList(); + + 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(); + } + break; + case "onFindResultReceived": + if ((webviewParams != null && + (webviewParams!.onFindResultReceived != null || + (webviewParams!.findInteractionController != null && + webviewParams!.findInteractionController!.params + .onFindResultReceived != + null))) || + _inAppBrowserEventHandler != null) { + int activeMatchOrdinal = call.arguments["activeMatchOrdinal"]; + int numberOfMatches = call.arguments["numberOfMatches"]; + bool isDoneCounting = call.arguments["isDoneCounting"]; + if (webviewParams != null) { + if (webviewParams!.findInteractionController != null && + webviewParams!.findInteractionController!.params + .onFindResultReceived != + null) + webviewParams! + .findInteractionController!.params.onFindResultReceived!( + webviewParams!.findInteractionController!, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting); + else + webviewParams!.onFindResultReceived!(_controllerFromPlatform, + activeMatchOrdinal, numberOfMatches, isDoneCounting); + } else { + if (_inAppBrowser!.findInteractionController != null && + _inAppBrowser! + .findInteractionController!.onFindResultReceived != + null) + _inAppBrowser!.findInteractionController!.onFindResultReceived!( + webviewParams!.findInteractionController!, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting); + else + _inAppBrowserEventHandler!.onFindResultReceived( + activeMatchOrdinal, numberOfMatches, isDoneCounting); + } + } + break; + case "onPermissionRequest": + if ((webviewParams != null && + (webviewParams!.onPermissionRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnPermissionRequest != null)) || + _inAppBrowserEventHandler != null) { + String origin = call.arguments["origin"]; + List resources = call.arguments["resources"].cast(); + + Map arguments = + call.arguments.cast(); + PermissionRequest permissionRequest = + PermissionRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onPermissionRequest != null) + return (await webviewParams!.onPermissionRequest!( + _controllerFromPlatform, permissionRequest)) + ?.toMap(); + else { + return (await webviewParams!.androidOnPermissionRequest!( + _controllerFromPlatform, origin, resources)) + ?.toMap(); + } + } else { + return (await _inAppBrowserEventHandler! + .onPermissionRequest(permissionRequest)) + ?.toMap() ?? + (await _inAppBrowserEventHandler! + .androidOnPermissionRequest(origin, resources)) + ?.toMap(); + } + } + break; + case "onUpdateVisitedHistory": + if ((webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + bool? isReload = call.arguments["isReload"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) + webviewParams!.onUpdateVisitedHistory!( + _controllerFromPlatform, uri, isReload); + else + _inAppBrowserEventHandler!.onUpdateVisitedHistory(uri, isReload); + } + break; + case "onWebContentProcessDidTerminate": + if (webviewParams != null && + (webviewParams!.onWebContentProcessDidTerminate != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosOnWebContentProcessDidTerminate != null)) { + if (webviewParams!.onWebContentProcessDidTerminate != null) + webviewParams! + .onWebContentProcessDidTerminate!(_controllerFromPlatform); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams! + .iosOnWebContentProcessDidTerminate!(_controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onWebContentProcessDidTerminate(); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.iosOnWebContentProcessDidTerminate(); + } + 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 "onDidReceiveServerRedirectForProvisionalNavigation": + if (webviewParams != null && + (webviewParams! + .onDidReceiveServerRedirectForProvisionalNavigation != + null || + params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .iosOnDidReceiveServerRedirectForProvisionalNavigation != + null)) { + if (webviewParams! + .onDidReceiveServerRedirectForProvisionalNavigation != + null) + webviewParams!.onDidReceiveServerRedirectForProvisionalNavigation!( + _controllerFromPlatform); + else { + params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .iosOnDidReceiveServerRedirectForProvisionalNavigation!( + _controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler! + .onDidReceiveServerRedirectForProvisionalNavigation(); + _inAppBrowserEventHandler! + .iosOnDidReceiveServerRedirectForProvisionalNavigation(); + } + break; + case "onNavigationResponse": + if ((webviewParams != null && + (webviewParams!.onNavigationResponse != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosOnNavigationResponse != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + // ignore: deprecated_member_use_from_same_package + IOSWKNavigationResponse iosOnNavigationResponse = + // ignore: deprecated_member_use_from_same_package + IOSWKNavigationResponse.fromMap(arguments)!; + + NavigationResponse navigationResponse = + NavigationResponse.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onNavigationResponse != null) + return (await webviewParams!.onNavigationResponse!( + _controllerFromPlatform, navigationResponse)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.iosOnNavigationResponse!( + _controllerFromPlatform, iosOnNavigationResponse)) + ?.toNativeValue(); + } + } else { + return (await _inAppBrowserEventHandler! + .onNavigationResponse(navigationResponse)) + ?.toNativeValue() ?? + (await _inAppBrowserEventHandler! + .iosOnNavigationResponse(iosOnNavigationResponse)) + ?.toNativeValue(); + } + } + break; + case "shouldAllowDeprecatedTLS": + if ((webviewParams != null && + (webviewParams!.shouldAllowDeprecatedTLS != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosShouldAllowDeprecatedTLS != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + URLAuthenticationChallenge challenge = + URLAuthenticationChallenge.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.shouldAllowDeprecatedTLS != null) + return (await webviewParams!.shouldAllowDeprecatedTLS!( + _controllerFromPlatform, challenge)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.iosShouldAllowDeprecatedTLS!( + _controllerFromPlatform, challenge)) + ?.toNativeValue(); + } + } else { + return (await _inAppBrowserEventHandler! + .shouldAllowDeprecatedTLS(challenge)) + ?.toNativeValue() ?? + // ignore: deprecated_member_use_from_same_package + (await _inAppBrowserEventHandler! + .iosShouldAllowDeprecatedTLS(challenge)) + ?.toNativeValue(); + } + } + break; + case "onLongPressHitTestResult": + if ((webviewParams != null && + webviewParams!.onLongPressHitTestResult != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + InAppWebViewHitTestResult hitTestResult = + InAppWebViewHitTestResult.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onLongPressHitTestResult != null) + webviewParams!.onLongPressHitTestResult!( + _controllerFromPlatform, hitTestResult); + else + _inAppBrowserEventHandler!.onLongPressHitTestResult(hitTestResult); + } + 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) { + int? androidId = call.arguments["androidId"]; + String? iosId = call.arguments["iosId"]; + dynamic id = call.arguments["id"]; + String title = call.arguments["title"]; + + ContextMenuItem menuItemClicked = ContextMenuItem( + id: id, + // ignore: deprecated_member_use_from_same_package + androidId: androidId, + // ignore: deprecated_member_use_from_same_package + iosId: iosId, + title: title, + action: null); + + 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 "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 "onOverScrolled": + if ((webviewParams != null && webviewParams!.onOverScrolled != null) || + _inAppBrowserEventHandler != null) { + int x = call.arguments["x"]; + int y = call.arguments["y"]; + bool clampedX = call.arguments["clampedX"]; + bool clampedY = call.arguments["clampedY"]; + + if (webviewParams != null && webviewParams!.onOverScrolled != null) + webviewParams!.onOverScrolled!( + _controllerFromPlatform, x, y, clampedX, clampedY); + else + _inAppBrowserEventHandler!.onOverScrolled(x, y, clampedX, clampedY); + } + break; + case "onWindowFocus": + if (webviewParams != null && webviewParams!.onWindowFocus != null) + webviewParams!.onWindowFocus!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowFocus(); + break; + case "onWindowBlur": + if (webviewParams != null && webviewParams!.onWindowBlur != null) + webviewParams!.onWindowBlur!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowBlur(); + 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"]; + String? printJobId = call.arguments["printJobId"]; + WebUri? uri = url != null ? WebUri(url) : null; + WindowsPrintJobController? printJob = printJobId != null + ? WindowsPrintJobController( + WindowsPrintJobControllerCreationParams(id: printJobId)) + : null; + + if (webviewParams != null) { + if (webviewParams!.onPrintRequest != null) + return await webviewParams!.onPrintRequest!( + _controllerFromPlatform, uri, printJob); + 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, printJob); + } + } + break; + case "onInjectedScriptLoaded": + String id = call.arguments[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onLoadCallback != null) { + onLoadCallback(); + } + break; + case "onInjectedScriptError": + String id = call.arguments[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onErrorCallback != null) { + onErrorCallback(); + } + 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 "onContentSizeChanged": + if ((webviewParams != null && + webviewParams!.onContentSizeChanged != null) || + _inAppBrowserEventHandler != null) { + var oldContentSize = MapSize.fromMap( + call.arguments["oldContentSize"]?.cast())!; + var newContentSize = MapSize.fromMap( + call.arguments["newContentSize"]?.cast())!; + + if (webviewParams != null && + webviewParams!.onContentSizeChanged != null) + webviewParams!.onContentSizeChanged!( + _controllerFromPlatform, oldContentSize, newContentSize); + else + _inAppBrowserEventHandler! + .onContentSizeChanged(oldContentSize, newContentSize); + } + break; + case "onDevToolsProtocolEventReceived": + String eventName = call.arguments["eventName"]; + dynamic data = call.arguments["data"] != null + ? jsonDecode(call.arguments["data"]) + : null; + + if (this._devToolsProtocolEventListenerMap.containsKey(eventName)) { + this._devToolsProtocolEventListenerMap[eventName]!.call(data); + } + break; + case "onProcessFailed": + if ((webviewParams != null && webviewParams!.onProcessFailed != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + final detail = ProcessFailedDetail.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onProcessFailed != null) + webviewParams!.onProcessFailed!(_controllerFromPlatform, detail); + else + _inAppBrowserEventHandler!.onProcessFailed(detail); + } + break; + case "onAcceleratorKeyPressed": + if ((webviewParams != null && + webviewParams!.onAcceleratorKeyPressed != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + final detail = AcceleratorKeyPressedDetail.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAcceleratorKeyPressed != null) + webviewParams!.onAcceleratorKeyPressed!( + _controllerFromPlatform, detail); + else + _inAppBrowserEventHandler!.onAcceleratorKeyPressed(detail); + } + 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 params.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; + } + + 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 getProgress() async { + Map args = {}; + return await channel?.invokeMethod('getProgress', args); + } + + @override + Future getHtml() async { + String? html; + + InAppWebViewSettings? settings = await getSettings(); + if (settings != null && settings.javaScriptEnabled == true) { + html = await evaluateJavascript( + source: "window.document.getElementsByTagName('html')[0].outerHTML;"); + if (html != null && html.isNotEmpty) return html; + } + + var webviewUrl = await getUrl(); + if (webviewUrl == null) { + return html; + } + + if (webviewUrl.isScheme("file")) { + var assetPathSplit = webviewUrl.toString().split("/flutter_assets/"); + var assetPath = assetPathSplit[assetPathSplit.length - 1]; + try { + var bytes = await rootBundle.load(assetPath); + html = utf8.decode(bytes.buffer.asUint8List()); + } catch (e) {} + } else { + try { + HttpClient client = HttpClient(); + var htmlRequest = await client.getUrl(webviewUrl); + html = + await (await htmlRequest.close()).transform(Utf8Decoder()).join(); + } catch (e) { + developer.log(e.toString(), name: this.runtimeType.toString()); + } + } + + return html; + } + + @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) { + List> links = (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; +})(); +"""))?.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 loadUrl( + {required URLRequest urlRequest, + @Deprecated('Use allowingReadAccessTo instead') + Uri? iosAllowingReadAccessTo, + WebUri? allowingReadAccessTo}) async { + assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); + assert( + allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file")); + assert(iosAllowingReadAccessTo == null || + iosAllowingReadAccessTo.isScheme("file")); + + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent( + 'allowingReadAccessTo', + () => + allowingReadAccessTo?.toString() ?? + iosAllowingReadAccessTo?.toString()); + await channel?.invokeMethod('loadUrl', args); + } + + @override + Future postUrl( + {required WebUri url, required Uint8List postData}) async { + assert(url.toString().isNotEmpty); + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('postData', () => postData); + await channel?.invokeMethod('postUrl', 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 { + assert( + allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file")); + assert(iosAllowingReadAccessTo == null || + iosAllowingReadAccessTo.isScheme("file")); + + Map args = {}; + args.putIfAbsent('data', () => data); + args.putIfAbsent('mimeType', () => mimeType); + args.putIfAbsent('encoding', () => encoding); + args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank"); + args.putIfAbsent( + 'historyUrl', + () => + historyUrl?.toString() ?? + androidHistoryUrl?.toString() ?? + "about:blank"); + args.putIfAbsent( + 'allowingReadAccessTo', + () => + allowingReadAccessTo?.toString() ?? + iosAllowingReadAccessTo?.toString()); + await channel?.invokeMethod('loadData', args); + } + + @override + Future loadFile({required String assetFilePath}) async { + assert(assetFilePath.isNotEmpty); + Map args = {}; + args.putIfAbsent('assetFilePath', () => assetFilePath); + await channel?.invokeMethod('loadFile', args); + } + + @override + Future reload() async { + Map args = {}; + await channel?.invokeMethod('reload', args); + } + + @override + Future goBack() async { + Map args = {}; + await channel?.invokeMethod('goBack', args); + } + + @override + Future canGoBack() async { + Map args = {}; + return await channel?.invokeMethod('canGoBack', args) ?? false; + } + + @override + Future goForward() async { + Map args = {}; + await channel?.invokeMethod('goForward', args); + } + + @override + Future canGoForward() async { + Map args = {}; + return await channel?.invokeMethod('canGoForward', args) ?? false; + } + + @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 goTo({required WebHistoryItem historyItem}) async { + var steps = historyItem.offset; + if (steps != null) { + await goBackOrForward(steps: steps); + } + } + + @override + Future isLoading() async { + Map args = {}; + return await channel?.invokeMethod('isLoading', args) ?? false; + } + + @override + Future stopLoading() async { + Map args = {}; + await channel?.invokeMethod('stopLoading', args); + } + + @override + Future evaluateJavascript( + {required String source, ContentWorld? contentWorld}) async { + Map args = {}; + args.putIfAbsent('source', () => source); + args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); + var data = await channel?.invokeMethod('evaluateJavascript', args); + if (data != null) { + try { + // try to json decode the data coming from JavaScript + // otherwise return it as it is. + data = json.decode(data); + } catch (e) {} + } + return data; + } + + @override + Future injectJavascriptFileFromUrl( + {required WebUri urlFile, + ScriptHtmlTagAttributes? scriptHtmlTagAttributes}) async { + assert(urlFile.toString().isNotEmpty); + var id = scriptHtmlTagAttributes?.id; + if (scriptHtmlTagAttributes != null && id != null) { + _injectedScriptsFromURL[id] = scriptHtmlTagAttributes; + } + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap()); + await channel?.invokeMethod('injectJavascriptFileFromUrl', args); + } + + @override + Future injectJavascriptFileFromAsset( + {required String assetFilePath}) async { + String source = await rootBundle.loadString(assetFilePath); + return await evaluateJavascript(source: source); + } + + @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 { + assert(urlFile.toString().isNotEmpty); + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap()); + await channel?.invokeMethod('injectCSSFileFromUrl', args); + } + + @override + Future injectCSSFileFromAsset({required String assetFilePath}) async { + String source = await rootBundle.loadString(assetFilePath); + await injectCSSCode(source: source); + } + + @override + void addJavaScriptHandler( + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), + '"$handlerName" is a forbidden name!'); + this._javaScriptHandlersMap[handlerName] = (callback); + } + + @override + Function? removeJavaScriptHandler({required String handlerName}) { + return this._javaScriptHandlersMap.remove(handlerName); + } + + @override + bool hasJavaScriptHandler({required String handlerName}) { + return this._javaScriptHandlersMap.containsKey(handlerName); + } + + @override + Future takeScreenshot( + {ScreenshotConfiguration? screenshotConfiguration}) async { + Map args = {}; + args.putIfAbsent( + 'screenshotConfiguration', () => screenshotConfiguration?.toMap()); + final base64 = await channel?.invokeMethod('takeScreenshot', args); + return base64 != null ? base64Decode(base64) : null; + } + + @override + @Deprecated('Use setSettings instead') + Future setOptions({required InAppWebViewGroupOptions options}) async { + InAppWebViewSettings settings = + InAppWebViewSettings.fromMap(options.toMap()) ?? InAppWebViewSettings(); + await setSettings(settings: settings); + } + + @override + @Deprecated('Use getSettings instead') + Future getOptions() async { + InAppWebViewSettings? settings = await getSettings(); + + Map? options = settings?.toMap(); + if (options != null) { + options = options.cast(); + return InAppWebViewGroupOptions.fromMap(options as Map); + } + + return null; + } + + @override + Future setSettings({required InAppWebViewSettings settings}) async { + Map args = {}; + + args.putIfAbsent('settings', () => settings.toMap()); + await channel?.invokeMethod('setSettings', 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 getCopyBackForwardList() async { + Map args = {}; + Map? result = + (await channel?.invokeMethod('getCopyBackForwardList', args)) + ?.cast(); + return WebHistory.fromMap(result); + } + + @override + @Deprecated("Use InAppWebViewController.clearAllCache instead") + Future clearCache() async { + Map args = {}; + await channel?.invokeMethod('clearCache', args); + } + + @override + @Deprecated("Use FindInteractionController.findAll instead") + Future findAllAsync({required String find}) async { + Map args = {}; + args.putIfAbsent('find', () => find); + await channel?.invokeMethod('findAll', args); + } + + @override + @Deprecated("Use FindInteractionController.findNext instead") + Future findNext({required bool forward}) async { + Map args = {}; + args.putIfAbsent('forward', () => forward); + await channel?.invokeMethod('findNext', args); + } + + @override + @Deprecated("Use FindInteractionController.clearMatches instead") + Future clearMatches() async { + Map args = {}; + await channel?.invokeMethod('clearMatches', args); + } + + @override + @Deprecated("Use tRexRunnerHtml instead") + Future getTRexRunnerHtml() async { + return await tRexRunnerHtml; + } + + @override + @Deprecated("Use tRexRunnerCss instead") + Future getTRexRunnerCss() async { + return await tRexRunnerCss; + } + + @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 pauseTimers() async { + Map args = {}; + await channel?.invokeMethod('pauseTimers', args); + } + + @override + Future resumeTimers() async { + Map args = {}; + await channel?.invokeMethod('resumeTimers', args); + } + + @override + Future printCurrentPage( + {PrintJobSettings? settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings?.toMap()); + String? jobId = + await channel?.invokeMethod('printCurrentPage', args); + if (jobId != null) { + return WindowsPrintJobController( + PlatformPrintJobControllerCreationParams(id: jobId)); + } + return null; + } + + @override + Future getContentHeight() async { + Map args = {}; + var height = await channel?.invokeMethod('getContentHeight', args); + if (height == null || height == 0) { + // try to use javascript + var scrollHeight = await evaluateJavascript( + source: "document.documentElement.scrollHeight;"); + if (scrollHeight != null && scrollHeight is num) { + height = scrollHeight.toInt(); + } + } + return height; + } + + @override + Future getContentWidth() async { + Map args = {}; + var height = await channel?.invokeMethod('getContentWidth', args); + if (height == null || height == 0) { + // try to use javascript + var scrollHeight = await evaluateJavascript( + source: "document.documentElement.scrollWidth;"); + if (scrollHeight != null && scrollHeight is num) { + height = scrollHeight.toInt(); + } + } + return height; + } + + @override + Future zoomBy( + {required double zoomFactor, + @Deprecated('Use animated instead') bool? iosAnimated, + bool animated = false}) async { + Map args = {}; + args.putIfAbsent('zoomFactor', () => zoomFactor); + args.putIfAbsent('animated', () => iosAnimated ?? animated); + return await channel?.invokeMethod('zoomBy', args); + } + + @override + Future getOriginalUrl() async { + Map args = {}; + String? url = await channel?.invokeMethod('getOriginalUrl', args); + return url != null ? WebUri(url) : null; + } + + @override + @Deprecated('Use getZoomScale instead') + Future getScale() async { + return await getZoomScale(); + } + + @override + Future getSelectedText() async { + Map args = {}; + return await channel?.invokeMethod('getSelectedText', 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 { + Color? themeColor; + + try { + Map args = {}; + themeColor = UtilColor.fromStringRepresentation( + await channel?.invokeMethod('getMetaThemeColor', args)); + return themeColor; + } catch (e) { + // not implemented + } + + // try using javascript + var metaTags = await getMetaTags(); + MetaTag? metaTagThemeColor; + + for (var metaTag in metaTags) { + if (metaTag.name == "theme-color") { + metaTagThemeColor = metaTag; + break; + } + } + + if (metaTagThemeColor == null) { + return null; + } + + var colorValue = metaTagThemeColor.content; + + themeColor = colorValue != null + ? UtilColor.fromStringRepresentation(colorValue) + : null; + + return themeColor; + } + + @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 getCertificate() async { + Map args = {}; + Map? sslCertificateMap = + (await channel?.invokeMethod('getCertificate', args)) + ?.cast(); + if (sslCertificateMap != null && + sslCertificateMap['x509Certificate'] != null) { + sslCertificateMap['x509Certificate'] = + base64Decode(sslCertificateMap['x509Certificate']); + } + return SslCertificate.fromMap(sslCertificateMap); + } + + @override + Future addUserScript({required UserScript userScript}) async { + assert(webviewParams?.windowId == null); + + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ?? + false)) { + _userScripts[userScript.injectionTime]?.add(userScript); + await channel?.invokeMethod('addUserScript', args); + } + } + + @override + Future addUserScripts({required List userScripts}) async { + assert(webviewParams?.windowId == null); + + for (var i = 0; i < userScripts.length; i++) { + await addUserScript(userScript: userScripts[i]); + } + } + + @override + Future removeUserScript({required UserScript userScript}) async { + assert(webviewParams?.windowId == null); + + var index = _userScripts[userScript.injectionTime]?.indexOf(userScript); + if (index == null || index == -1) { + return false; + } + + _userScripts[userScript.injectionTime]?.remove(userScript); + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + args.putIfAbsent('index', () => index); + await channel?.invokeMethod('removeUserScript', args); + + return true; + } + + @override + Future removeUserScriptsByGroupName({required String groupName}) async { + assert(webviewParams?.windowId == null); + + final List userScriptsAtDocumentStart = List.from( + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] ?? []); + for (final userScript in userScriptsAtDocumentStart) { + if (userScript.groupName == groupName) { + _userScripts[userScript.injectionTime]?.remove(userScript); + } + } + + final List userScriptsAtDocumentEnd = + List.from(_userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] ?? []); + for (final userScript in userScriptsAtDocumentEnd) { + if (userScript.groupName == groupName) { + _userScripts[userScript.injectionTime]?.remove(userScript); + } + } + + Map args = {}; + args.putIfAbsent('groupName', () => groupName); + await channel?.invokeMethod('removeUserScriptsByGroupName', args); + } + + @override + Future removeUserScripts( + {required List userScripts}) async { + assert(webviewParams?.windowId == null); + + for (final userScript in userScripts) { + await removeUserScript(userScript: userScript); + } + } + + @override + Future removeAllUserScripts() async { + assert(webviewParams?.windowId == null); + + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.clear(); + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); + + Map args = {}; + await channel?.invokeMethod('removeAllUserScripts', args); + } + + @override + bool hasUserScript({required UserScript userScript}) { + return _userScripts[userScript.injectionTime]?.contains(userScript) ?? + false; + } + + @override + Future callAsyncJavaScript( + {required String functionBody, + Map arguments = const {}, + ContentWorld? contentWorld}) async { + Map args = {}; + args.putIfAbsent('functionBody', () => functionBody); + args.putIfAbsent('arguments', () => jsonEncode(arguments)); + args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); + var data = await channel?.invokeMethod('callAsyncJavaScript', args); + if (data == null) { + return null; + } + data = json.decode(data); + return CallAsyncJavaScriptResult( + value: data["value"], error: data["error"]); + } + + @override + Future saveWebArchive( + {required String filePath, bool autoname = false}) async { + if (!autoname) { + assert( + filePath.endsWith("." + WebArchiveFormat.WEBARCHIVE.toNativeValue())); + } + + Map args = {}; + args.putIfAbsent("filePath", () => filePath); + args.putIfAbsent("autoname", () => autoname); + return await channel?.invokeMethod('saveWebArchive', args); + } + + @override + Future isSecureContext() async { + Map args = {}; + return await channel?.invokeMethod('isSecureContext', args) ?? false; + } + + @override + Future createWebMessageChannel() async { + Map args = {}; + Map? result = + (await channel?.invokeMethod('createWebMessageChannel', args)) + ?.cast(); + final webMessageChannel = WindowsWebMessageChannel.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 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 WindowsWebMessageListener); + _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 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 reloadFromOrigin() async { + Map args = {}; + await channel?.invokeMethod('reloadFromOrigin', args); + } + + @override + Future createPdf( + {@Deprecated("Use pdfConfiguration instead") + // ignore: deprecated_member_use_from_same_package + IOSWKPDFConfiguration? iosWKPdfConfiguration, + PDFConfiguration? pdfConfiguration}) async { + Map args = {}; + args.putIfAbsent('pdfConfiguration', + () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); + return await channel?.invokeMethod('createPdf', args); + } + + @override + Future createWebArchiveData() async { + Map args = {}; + return await channel?.invokeMethod('createWebArchiveData', args); + } + + @override + Future hasOnlySecureContent() async { + Map args = {}; + return await channel?.invokeMethod('hasOnlySecureContent', args) ?? + false; + } + + @override + Future pauseAllMediaPlayback() async { + Map args = {}; + return await channel?.invokeMethod('pauseAllMediaPlayback', args); + } + + @override + Future setAllMediaPlaybackSuspended({required bool suspended}) async { + Map args = {}; + args.putIfAbsent("suspended", () => suspended); + return await channel?.invokeMethod('setAllMediaPlaybackSuspended', args); + } + + @override + Future closeAllMediaPresentations() async { + Map args = {}; + return await channel?.invokeMethod('closeAllMediaPresentations', args); + } + + @override + Future requestMediaPlaybackState() async { + Map args = {}; + return MediaPlaybackState.fromNativeValue( + await channel?.invokeMethod('requestMediaPlaybackState', args)); + } + + @override + Future isInFullscreen() async { + Map args = {}; + return await channel?.invokeMethod('isInFullscreen', args) ?? false; + } + + @override + Future getCameraCaptureState() async { + Map args = {}; + return MediaCaptureState.fromNativeValue( + await channel?.invokeMethod('getCameraCaptureState', args)); + } + + @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 = {}; + return MediaCaptureState.fromNativeValue( + await channel?.invokeMethod('getMicrophoneCaptureState', args)); + } + + @override + Future setMicrophoneCaptureState( + {required MediaCaptureState state}) async { + Map args = {}; + args.putIfAbsent('state', () => state.toNativeValue()); + await channel?.invokeMethod('setMicrophoneCaptureState', args); + } + + @override + Future loadSimulatedRequest( + {required URLRequest urlRequest, + required Uint8List data, + URLResponse? urlResponse}) async { + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent('data', () => data); + args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); + await channel?.invokeMethod('loadSimulatedRequest', args); + } + + @override + Future openDevTools() async { + Map args = {}; + await channel?.invokeMethod('openDevTools', args); + } + + @override + Future callDevToolsProtocolMethod( + {required String methodName, Map? parameters}) async { + Map args = {}; + args.putIfAbsent('methodName', () => methodName); + args.putIfAbsent('parametersAsJson', + () => parameters != null ? jsonEncode(parameters) : null); + final result = + await channel?.invokeMethod('callDevToolsProtocolMethod', args); + if (result != null) { + return jsonDecode(result); + } + return null; + } + + @override + Future addDevToolsProtocolEventListener( + {required String eventName, + required Function(dynamic data) callback}) async { + Map args = {}; + args.putIfAbsent('eventName', () => eventName); + await channel?.invokeMethod('addDevToolsProtocolEventListener', args); + this._devToolsProtocolEventListenerMap[eventName] = callback; + } + + @override + Future removeDevToolsProtocolEventListener( + {required String eventName}) async { + Map args = {}; + args.putIfAbsent('eventName', () => eventName); + await channel?.invokeMethod('removeDevToolsProtocolEventListener', args); + this._devToolsProtocolEventListenerMap.remove(eventName); + } + + @override + Future clearSslPreferences() async { + Map args = {}; + await channel?.invokeMethod('clearSslPreferences', args); + } + + @override + Future pause() async { + Map args = {}; + await channel?.invokeMethod('pause', args); + } + + @override + Future resume() async { + Map args = {}; + await channel?.invokeMethod('resume', args); + } + + @override + Future isInterfaceSupported(WebViewInterface interface) async { + Map args = {}; + args.putIfAbsent('interface', () => interface.toNativeValue()); + return await channel?.invokeMethod('isInterfaceSupported', args) ?? + false; + } + + @override + Future getDefaultUserAgent() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getDefaultUserAgent', args) ?? + ''; + } + + @override + Future handlesURLScheme(String urlScheme) async { + Map args = {}; + args.putIfAbsent('urlScheme', () => urlScheme); + return await _staticChannel.invokeMethod('handlesURLScheme', args); + } + + @override + Future disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async { + Map args = {}; + args.putIfAbsent('keepAliveId', () => keepAlive.id); + await _staticChannel.invokeMethod('disposeKeepAlive', args); + _keepAliveMap[keepAlive] = 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) ?? + ''; + } + + @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 + void dispose({bool isKeepAlive = false}) { + disposeChannel(removeMethodCallHandler: !isKeepAlive); + _inAppBrowser = null; + webStorage.dispose(); + if (!isKeepAlive) { + _controllerFromPlatform = null; + _javaScriptHandlersMap.clear(); + _userScripts.clear(); + _webMessageListenerObjNames.clear(); + _injectedScriptsFromURL.clear(); + for (final webMessageChannel in _webMessageChannels) { + webMessageChannel.dispose(); + } + _webMessageChannels.clear(); + for (final webMessageListener in _webMessageListeners) { + webMessageListener.dispose(); + } + _webMessageListeners.clear(); + _devToolsProtocolEventListenerMap.clear(); + } + } +} + +extension InternalInAppWebViewController on WindowsInAppWebViewController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/main.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/main.dart new file mode 100644 index 000000000..b83b0611e --- /dev/null +++ b/flutter_inappwebview_windows/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/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart new file mode 100644 index 000000000..b3ac05646 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart @@ -0,0 +1,143 @@ +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'cookie_manager.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 'webview_environment/webview_environment.dart'; +import 'web_storage/web_storage.dart'; + +/// Implementation of [InAppWebViewPlatform] using the WebKit API. +class WindowsInAppWebViewPlatform extends InAppWebViewPlatform { + /// Registers this class as the default instance of [InAppWebViewPlatform]. + static void registerWith() { + InAppWebViewPlatform.instance = WindowsInAppWebViewPlatform(); + } + + /// Creates a new [WindowsCookieManager]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [CookieManager] in `flutter_inappwebview` instead. + @override + WindowsCookieManager createPlatformCookieManager( + PlatformCookieManagerCreationParams params, + ) { + return WindowsCookieManager(params); + } + + /// Creates a new [WindowsInAppWebViewController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params, + ) { + return WindowsInAppWebViewController(params); + } + + /// Creates a new empty [WindowsInAppWebViewController] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewControllerStatic() { + return WindowsInAppWebViewController.static(); + } + + /// Creates a new [WindowsInAppWebViewWidget]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewWidget createPlatformInAppWebViewWidget( + PlatformInAppWebViewWidgetCreationParams params, + ) { + return WindowsInAppWebViewWidget(params); + } + + /// Creates a new [WindowsInAppBrowser]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppBrowser] in `flutter_inappwebview` instead. + @override + WindowsInAppBrowser createPlatformInAppBrowser( + PlatformInAppBrowserCreationParams params, + ) { + return WindowsInAppBrowser(params); + } + + /// Creates a new empty [WindowsInAppBrowser] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppBrowser] in `flutter_inappwebview` instead. + @override + WindowsInAppBrowser createPlatformInAppBrowserStatic() { + return WindowsInAppBrowser.static(); + } + + /// Creates a new [WindowsHeadlessInAppWebView]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [HeadlessInAppWebView] in `flutter_inappwebview` instead. + @override + WindowsHeadlessInAppWebView createPlatformHeadlessInAppWebView( + PlatformHeadlessInAppWebViewCreationParams params, + ) { + return WindowsHeadlessInAppWebView(params); + } + + /// Creates a new [WindowsWebViewEnvironment]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + @override + WindowsWebViewEnvironment createPlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params, + ) { + return WindowsWebViewEnvironment(params); + } + + /// Creates a new empty [WindowsWebViewEnvironment] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + @override + WindowsWebViewEnvironment createPlatformWebViewEnvironmentStatic() { + return WindowsWebViewEnvironment.static(); + } + + /// Creates a new [WindowsWebStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. + @override + WindowsWebStorage createPlatformWebStorage( + PlatformWebStorageCreationParams params, + ) { + return WindowsWebStorage(params); + } + + /// Creates a new [WindowsLocalStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. + @override + WindowsLocalStorage createPlatformLocalStorage( + PlatformLocalStorageCreationParams params, + ) { + return WindowsLocalStorage(params); + } + + /// Creates a new [WindowsSessionStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. + @override + WindowsSessionStorage createPlatformSessionStorage( + PlatformSessionStorageCreationParams params, + ) { + return WindowsSessionStorage(params); + } +} diff --git a/flutter_inappwebview_windows/lib/src/main.dart b/flutter_inappwebview_windows/lib/src/main.dart new file mode 100644 index 000000000..4fce8f8c0 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/main.dart @@ -0,0 +1,11 @@ +export 'inappwebview_platform.dart'; +export 'in_app_webview/main.dart'; +export 'in_app_browser/main.dart'; +export 'web_storage/main.dart'; +export 'cookie_manager.dart' hide InternalCookieManager; +export 'http_auth_credentials_database.dart' + hide InternalHttpAuthCredentialDatabase; +export 'web_message/main.dart'; +export 'print_job/main.dart'; +export 'find_interaction/main.dart'; +export 'webview_environment/main.dart'; diff --git a/flutter_inappwebview_windows/lib/src/platform_util.dart b/flutter_inappwebview_windows/lib/src/platform_util.dart new file mode 100644 index 000000000..3f3b7c8fa --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/platform_util.dart @@ -0,0 +1,67 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +abstract mixin class PlatformUtilListener { + void onWindowMove() {} + void onWindowStartMove() {} + void onWindowEndMove() {} +} + +///Platform native utilities +class PlatformUtil { + static PlatformUtil? _instance; + static const MethodChannel _channel = + MethodChannel('com.pichillilorenzo/flutter_inappwebview_platformutil'); + + static final ObserverList _listeners = + ObserverList(); + + PlatformUtil._(); + + ///Get [PlatformUtil] instance. + static PlatformUtil instance() { + return (_instance != null) ? _instance! : _init(); + } + + static PlatformUtil _init() { + _channel.setMethodCallHandler((call) async { + try { + return await _handleMethod(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + _instance = PlatformUtil._(); + return _instance!; + } + + static Future _handleMethod(MethodCall call) async { + if (call.method == 'onEvent') { + String eventName = call.arguments['eventName']; + for (final listener in _listeners) { + switch (eventName) { + case 'onWindowMove': + listener.onWindowMove(); + break; + case 'onWindowStartMove': + listener.onWindowStartMove(); + break; + case 'onWindowEndMove': + listener.onWindowEndMove(); + break; + } + } + } + } + + /// Add a listener to the window. + void addListener(PlatformUtilListener listener) { + _listeners.add(listener); + } + + /// Remove a listener from the window. + void removeListener(PlatformUtilListener listener) { + _listeners.remove(listener); + } +} diff --git a/flutter_inappwebview_windows/lib/src/print_job/main.dart b/flutter_inappwebview_windows/lib/src/print_job/main.dart new file mode 100644 index 000000000..4e70ad940 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/print_job/main.dart @@ -0,0 +1 @@ +export 'print_job_controller.dart'; diff --git a/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart new file mode 100644 index 000000000..c32e48735 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart @@ -0,0 +1,72 @@ +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 [WindowsPrintJobController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformPrintJobControllerCreationParams] for +/// more information. +@immutable +class WindowsPrintJobControllerCreationParams + extends PlatformPrintJobControllerCreationParams { + /// Creates a new [WindowsPrintJobControllerCreationParams] instance. + const WindowsPrintJobControllerCreationParams( + {required super.id}); + + /// Creates a [WindowsPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. + factory WindowsPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformPrintJobControllerCreationParams params) { + return WindowsPrintJobControllerCreationParams( + id: params.id); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} +class WindowsPrintJobController extends PlatformPrintJobController + with ChannelController { + /// Constructs a [WindowsPrintJobController]. + WindowsPrintJobController(PlatformPrintJobControllerCreationParams params) + : super.implementation( + params is WindowsPrintJobControllerCreationParams + ? params + : WindowsPrintJobControllerCreationParams + .fromPlatformPrintJobControllerCreationParams(params), + ) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onComplete": + bool completed = call.arguments["completed"]; + String? error = call.arguments["error"]; + if (onComplete != null) { + onComplete!(completed, error); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + } + + @override + Future getInfo() async { + Map args = {}; + Map? infoMap = + (await channel?.invokeMethod('getInfo', args))?.cast(); + return PrintJobInfo.fromMap(infoMap); + } + + @override + Future dispose() async { + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/main.dart b/flutter_inappwebview_windows/lib/src/web_message/main.dart new file mode 100644 index 000000000..d41e30c75 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/main.dart @@ -0,0 +1,3 @@ +export 'web_message_port.dart' hide InternalWebMessagePort; +export 'web_message_channel.dart' hide InternalWebMessageChannel; +export 'web_message_listener.dart'; diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart new file mode 100644 index 000000000..19c99b315 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart @@ -0,0 +1,120 @@ +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 [WindowsWebMessageChannel]. +/// +/// 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 WindowsWebMessageChannelCreationParams + extends PlatformWebMessageChannelCreationParams { + /// Creates a new [WindowsWebMessageChannelCreationParams] instance. + const WindowsWebMessageChannelCreationParams( + {required super.id, required super.port1, required super.port2}); + + /// Creates a [WindowsWebMessageChannelCreationParams] instance based on [PlatformWebMessageChannelCreationParams]. + factory WindowsWebMessageChannelCreationParams.fromPlatformWebMessageChannelCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessageChannelCreationParams params) { + return WindowsWebMessageChannelCreationParams( + id: params.id, port1: params.port1, port2: params.port2); + } + + @override + String toString() { + return 'MacOSWebMessageChannelCreationParams{id: $id, port1: $port1, port2: $port2}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageChannel} +class WindowsWebMessageChannel extends PlatformWebMessageChannel + with ChannelController { + /// Constructs a [WindowsWebMessageChannel]. + WindowsWebMessageChannel(PlatformWebMessageChannelCreationParams params) + : super.implementation( + params is WindowsWebMessageChannelCreationParams + ? params + : WindowsWebMessageChannelCreationParams + .fromPlatformWebMessageChannelCreationParams(params), + ) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_${params.id}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + static final WindowsWebMessageChannel _staticValue = WindowsWebMessageChannel( + WindowsWebMessageChannelCreationParams( + id: '', + port1: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 0)), + port2: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 1)))); + + /// Provide static access. + factory WindowsWebMessageChannel.static() { + return _staticValue; + } + + WindowsWebMessagePort get _macosPort1 => port1 as WindowsWebMessagePort; + + WindowsWebMessagePort get _macosPort2 => port2 as WindowsWebMessagePort; + + static WindowsWebMessageChannel? _fromMap(Map? map) { + if (map == null) { + return null; + } + var webMessageChannel = WindowsWebMessageChannel( + WindowsWebMessageChannelCreationParams( + id: map["id"], + port1: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 0)), + port2: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 1)))); + webMessageChannel._macosPort1.webMessageChannel = webMessageChannel; + webMessageChannel._macosPort2.webMessageChannel = webMessageChannel; + return webMessageChannel; + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onMessage": + int index = call.arguments["index"]; + var port = index == 0 ? _macosPort1 : _macosPort2; + 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 + WindowsWebMessageChannel? fromMap(Map? map) { + return _fromMap(map); + } + + @override + void dispose() { + disposeChannel(); + } + + @override + String toString() { + return 'MacOSWebMessageChannel{id: $id, port1: $port1, port2: $port2}'; + } +} + +extension InternalWebMessageChannel on WindowsWebMessageChannel { + MethodChannel? get internalChannel => channel; +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart new file mode 100644 index 000000000..22a9cd768 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart @@ -0,0 +1,164 @@ +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 [WindowsWebMessageListener]. +/// +/// 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 WindowsWebMessageListenerCreationParams + extends PlatformWebMessageListenerCreationParams { + /// Creates a new [WindowsWebMessageListenerCreationParams] instance. + const WindowsWebMessageListenerCreationParams( + {required this.allowedOriginRules, + required super.jsObjectName, + super.onPostMessage}); + + /// Creates a [WindowsWebMessageListenerCreationParams] instance based on [PlatformWebMessageListenerCreationParams]. + factory WindowsWebMessageListenerCreationParams.fromPlatformWebMessageListenerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessageListenerCreationParams params) { + return WindowsWebMessageListenerCreationParams( + allowedOriginRules: params.allowedOriginRules ?? Set.from(["*"]), + jsObjectName: params.jsObjectName, + onPostMessage: params.onPostMessage); + } + + @override + final Set allowedOriginRules; + + @override + String toString() { + return 'MacOSWebMessageListenerCreationParams{jsObjectName: $jsObjectName, allowedOriginRules: $allowedOriginRules, onPostMessage: $onPostMessage}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageListener} +class WindowsWebMessageListener extends PlatformWebMessageListener + with ChannelController { + /// Constructs a [WindowsWebMessageListener]. + WindowsWebMessageListener(PlatformWebMessageListenerCreationParams params) + : super.implementation( + params is WindowsWebMessageListenerCreationParams + ? params + : WindowsWebMessageListenerCreationParams + .fromPlatformWebMessageListenerCreationParams(params), + ) { + assert(!this._macosParams.allowedOriginRules.contains(""), + "allowedOriginRules cannot contain empty strings"); + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${_id}_${params.jsObjectName}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + ///Message Listener ID used internally. + final String _id = IdGenerator.generate(); + + MacOSJavaScriptReplyProxy? _replyProxy; + + WindowsWebMessageListenerCreationParams get _macosParams => + params as WindowsWebMessageListenerCreationParams; + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onPostMessage": + if (_replyProxy == null) { + _replyProxy = MacOSJavaScriptReplyProxy( + 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": _macosParams.allowedOriginRules.toList(), + }; + } + + @override + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return 'MacOSWebMessageListener{id: ${_id}, jsObjectName: ${params.jsObjectName}, allowedOriginRules: ${params.allowedOriginRules}, replyProxy: $_replyProxy}'; + } +} + +/// Object specifying creation parameters for creating a [MacOSJavaScriptReplyProxy]. +/// +/// 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 MacOSJavaScriptReplyProxyCreationParams + extends PlatformJavaScriptReplyProxyCreationParams { + /// Creates a new [MacOSJavaScriptReplyProxyCreationParams] instance. + const MacOSJavaScriptReplyProxyCreationParams( + {required super.webMessageListener}); + + /// Creates a [MacOSJavaScriptReplyProxyCreationParams] instance based on [PlatformJavaScriptReplyProxyCreationParams]. + factory MacOSJavaScriptReplyProxyCreationParams.fromPlatformJavaScriptReplyProxyCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformJavaScriptReplyProxyCreationParams params) { + return MacOSJavaScriptReplyProxyCreationParams( + webMessageListener: params.webMessageListener); + } +} + +///{@macro flutter_inappwebview_platform_interface.JavaScriptReplyProxy} +class MacOSJavaScriptReplyProxy extends PlatformJavaScriptReplyProxy { + /// Constructs a [WindowsWebMessageListener]. + MacOSJavaScriptReplyProxy(PlatformJavaScriptReplyProxyCreationParams params) + : super.implementation( + params is MacOSJavaScriptReplyProxyCreationParams + ? params + : MacOSJavaScriptReplyProxyCreationParams + .fromPlatformJavaScriptReplyProxyCreationParams(params), + ); + + WindowsWebMessageListener get _macosWebMessageListener => + params.webMessageListener as WindowsWebMessageListener; + + @override + Future postMessage(WebMessage message) async { + Map args = {}; + args.putIfAbsent('message', () => message.toMap()); + await _macosWebMessageListener.channel?.invokeMethod('postMessage', args); + } + + @override + String toString() { + return 'MacOSJavaScriptReplyProxy{}'; + } +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart new file mode 100644 index 000000000..5a910bc01 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart @@ -0,0 +1,95 @@ +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 [WindowsWebMessagePort]. +/// +/// 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 WindowsWebMessagePortCreationParams + extends PlatformWebMessagePortCreationParams { + /// Creates a new [WindowsWebMessagePortCreationParams] instance. + const WindowsWebMessagePortCreationParams({required super.index}); + + /// Creates a [WindowsWebMessagePortCreationParams] instance based on [PlatformWebMessagePortCreationParams]. + factory WindowsWebMessagePortCreationParams.fromPlatformWebMessagePortCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessagePortCreationParams params) { + return WindowsWebMessagePortCreationParams(index: params.index); + } + + @override + String toString() { + return 'MacOSWebMessagePortCreationParams{index: $index}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort} +class WindowsWebMessagePort extends PlatformWebMessagePort { + WebMessageCallback? _onMessage; + late WindowsWebMessageChannel _webMessageChannel; + + /// Constructs a [WindowsWebMessagePort]. + WindowsWebMessagePort(PlatformWebMessagePortCreationParams params) + : super.implementation( + params is WindowsWebMessagePortCreationParams + ? params + : WindowsWebMessagePortCreationParams + .fromPlatformWebMessagePortCreationParams(params), + ); + + @override + Future setWebMessageCallback(WebMessageCallback? onMessage) async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + await _webMessageChannel.internalChannel + ?.invokeMethod('setWebMessageCallback', args); + this._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": this._webMessageChannel.params.id + }; + } + + @override + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'MacOSWebMessagePort{index: ${params.index}}'; + } +} + +extension InternalWebMessagePort on WindowsWebMessagePort { + WebMessageCallback? get onMessage => _onMessage; + void set onMessage(WebMessageCallback? value) => _onMessage = value; + + WindowsWebMessageChannel get webMessageChannel => _webMessageChannel; + void set webMessageChannel(WindowsWebMessageChannel value) => + _webMessageChannel = value; +} diff --git a/flutter_inappwebview_windows/lib/src/web_storage/main.dart b/flutter_inappwebview_windows/lib/src/web_storage/main.dart new file mode 100644 index 000000000..7265ae52c --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/main.dart @@ -0,0 +1,2 @@ +export 'web_storage.dart'; +export 'web_storage_manager.dart' hide InternalWebStorageManager; diff --git a/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart new file mode 100644 index 000000000..93e06ec2e --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart @@ -0,0 +1,257 @@ +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 [WindowsWebStorage]. +/// +/// 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 WindowsWebStorageCreationParams extends PlatformWebStorageCreationParams { + /// Creates a new [WindowsWebStorageCreationParams] instance. + WindowsWebStorageCreationParams( + {required super.localStorage, required super.sessionStorage}); + + /// Creates a [WindowsWebStorageCreationParams] instance based on [PlatformWebStorageCreationParams]. + factory WindowsWebStorageCreationParams.fromPlatformWebStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebStorageCreationParams params) { + return WindowsWebStorageCreationParams( + localStorage: params.localStorage, + sessionStorage: params.sessionStorage); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebStorage} +class WindowsWebStorage extends PlatformWebStorage { + /// Constructs a [WindowsWebStorage]. + WindowsWebStorage(PlatformWebStorageCreationParams params) + : super.implementation( + params is WindowsWebStorageCreationParams + ? params + : WindowsWebStorageCreationParams + .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 [WindowsStorage]. +/// +/// 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 WindowsStorageCreationParams extends PlatformStorageCreationParams { + /// Creates a new [WindowsStorageCreationParams] instance. + WindowsStorageCreationParams( + {required super.controller, required super.webStorageType}); + + /// Creates a [WindowsStorageCreationParams] instance based on [PlatformStorageCreationParams]. + factory WindowsStorageCreationParams.fromPlatformStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformStorageCreationParams params) { + return WindowsStorageCreationParams( + controller: params.controller, webStorageType: params.webStorageType); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformStorage} +abstract mixin class WindowsStorage implements PlatformStorage { + @override + WindowsInAppWebViewController? 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 [WindowsLocalStorage]. +/// +/// 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 WindowsLocalStorageCreationParams + extends PlatformLocalStorageCreationParams { + /// Creates a new [WindowsLocalStorageCreationParams] instance. + WindowsLocalStorageCreationParams(super.params); + + /// Creates a [WindowsLocalStorageCreationParams] instance based on [PlatformLocalStorageCreationParams]. + factory WindowsLocalStorageCreationParams.fromPlatformLocalStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformLocalStorageCreationParams params) { + return WindowsLocalStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformLocalStorage} +class WindowsLocalStorage extends PlatformLocalStorage with WindowsStorage { + /// Constructs a [WindowsLocalStorage]. + WindowsLocalStorage(PlatformLocalStorageCreationParams params) + : super.implementation( + params is WindowsLocalStorageCreationParams + ? params + : WindowsLocalStorageCreationParams + .fromPlatformLocalStorageCreationParams(params), + ); + + /// Default storage + factory WindowsLocalStorage.defaultStorage( + {required PlatformInAppWebViewController? controller}) { + return WindowsLocalStorage(WindowsLocalStorageCreationParams( + PlatformLocalStorageCreationParams(PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.LOCAL_STORAGE)))); + } + + @override + WindowsInAppWebViewController? get controller => + params.controller as WindowsInAppWebViewController?; +} + +/// Object specifying creation parameters for creating a [WindowsSessionStorage]. +/// +/// 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 WindowsSessionStorageCreationParams + extends PlatformSessionStorageCreationParams { + /// Creates a new [WindowsSessionStorageCreationParams] instance. + WindowsSessionStorageCreationParams(super.params); + + /// Creates a [WindowsSessionStorageCreationParams] instance based on [PlatformSessionStorageCreationParams]. + factory WindowsSessionStorageCreationParams.fromPlatformSessionStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformSessionStorageCreationParams params) { + return WindowsSessionStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformSessionStorage} +class WindowsSessionStorage extends PlatformSessionStorage with WindowsStorage { + /// Constructs a [WindowsSessionStorage]. + WindowsSessionStorage(PlatformSessionStorageCreationParams params) + : super.implementation( + params is WindowsSessionStorageCreationParams + ? params + : WindowsSessionStorageCreationParams + .fromPlatformSessionStorageCreationParams(params), + ); + + /// Default storage + factory WindowsSessionStorage.defaultStorage( + {required PlatformInAppWebViewController? controller}) { + return WindowsSessionStorage(WindowsSessionStorageCreationParams( + PlatformSessionStorageCreationParams(PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.SESSION_STORAGE)))); + } + + @override + WindowsInAppWebViewController? get controller => + params.controller as WindowsInAppWebViewController?; +} diff --git a/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart b/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart new file mode 100644 index 000000000..cd9c24e61 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart @@ -0,0 +1,134 @@ +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 [WindowsWebStorageManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebStorageManagerCreationParams] for +/// more information. +@immutable +class WindowsWebStorageManagerCreationParams + extends PlatformWebStorageManagerCreationParams { + /// Creates a new [WindowsWebStorageManagerCreationParams] instance. + const WindowsWebStorageManagerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformWebStorageManagerCreationParams params, + ) : super(); + + /// Creates a [WindowsWebStorageManagerCreationParams] instance based on [PlatformWebStorageManagerCreationParams]. + factory WindowsWebStorageManagerCreationParams.fromPlatformWebStorageManagerCreationParams( + PlatformWebStorageManagerCreationParams params) { + return WindowsWebStorageManagerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebStorageManager} +class WindowsWebStorageManager extends PlatformWebStorageManager + with ChannelController { + /// Creates a new [WindowsWebStorageManager]. + WindowsWebStorageManager(PlatformWebStorageManagerCreationParams params) + : super.implementation( + params is WindowsWebStorageManagerCreationParams + ? params + : WindowsWebStorageManagerCreationParams + .fromPlatformWebStorageManagerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_webstoragemanager'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsWebStorageManager? _instance; + + ///Gets the WebStorage manager shared instance. + static WindowsWebStorageManager instance() { + return (_instance != null) ? _instance! : _init(); + } + + static WindowsWebStorageManager _init() { + _instance = WindowsWebStorageManager(WindowsWebStorageManagerCreationParams( + const PlatformWebStorageManagerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future> fetchDataRecords( + {required Set dataTypes}) async { + List recordList = []; + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + List> records = + (await channel?.invokeMethod('fetchDataRecords', args)) + ?.cast>() ?? + []; + for (var record in records) { + List dataTypesString = record["dataTypes"].cast(); + Set dataTypes = Set(); + for (var dataTypeValue in dataTypesString) { + var dataType = WebsiteDataType.fromNativeValue(dataTypeValue); + if (dataType != null) { + dataTypes.add(dataType); + } + } + recordList.add(WebsiteDataRecord( + displayName: record["displayName"], dataTypes: dataTypes)); + } + return recordList; + } + + @override + Future removeDataFor( + {required Set dataTypes, + required List dataRecords}) async { + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + + List> recordList = []; + for (var record in dataRecords) { + recordList.add(record.toMap()); + } + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("recordList", () => recordList); + await channel?.invokeMethod('removeDataFor', args); + } + + @override + Future removeDataModifiedSince( + {required Set dataTypes, required DateTime date}) async { + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + + var timestamp = date.millisecondsSinceEpoch; + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("timestamp", () => timestamp); + await channel?.invokeMethod('removeDataModifiedSince', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalWebStorageManager on WindowsWebStorageManager { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/webview_environment/main.dart b/flutter_inappwebview_windows/lib/src/webview_environment/main.dart new file mode 100644 index 000000000..a018ec28b --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/webview_environment/main.dart @@ -0,0 +1 @@ +export 'webview_environment.dart' hide InternalWindowsWebViewEnvironment; diff --git a/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart new file mode 100644 index 000000000..482c9f332 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart @@ -0,0 +1,163 @@ +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 [WindowsWebViewEnvironment]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +@immutable +class WindowsWebViewEnvironmentCreationParams + extends PlatformWebViewEnvironmentCreationParams { + /// Creates a new [WindowsInAppWebViewControllerCreationParams] instance. + const WindowsWebViewEnvironmentCreationParams({super.settings}); + + /// Creates a [WindowsInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams]. + factory WindowsWebViewEnvironmentCreationParams.fromPlatformWebViewEnvironmentCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewEnvironmentCreationParams params) { + return WindowsWebViewEnvironmentCreationParams(settings: params.settings); + } +} + +///Controls a WebView Environment used by WebView instances. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +class WindowsWebViewEnvironment extends PlatformWebViewEnvironment + with ChannelController { + static final MethodChannel _staticChannel = + MethodChannel('com.pichillilorenzo/flutter_webview_environment'); + + @override + final String id = IdGenerator.generate(); + + WindowsWebViewEnvironment(PlatformWebViewEnvironmentCreationParams params) + : super.implementation(params is WindowsWebViewEnvironmentCreationParams + ? params + : WindowsWebViewEnvironmentCreationParams + .fromPlatformWebViewEnvironmentCreationParams(params)); + + static final WindowsWebViewEnvironment _staticValue = + WindowsWebViewEnvironment(WindowsWebViewEnvironmentCreationParams()); + + factory WindowsWebViewEnvironment.static() { + return _staticValue; + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.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) { + case 'onNewBrowserVersionAvailable': + if (onNewBrowserVersionAvailable != null) { + onNewBrowserVersionAvailable?.call(); + } + break; + case 'onBrowserProcessExited': + if (onBrowserProcessExited != null) { + Map arguments = + call.arguments.cast(); + final detail = BrowserProcessExitedDetail.fromMap(arguments)!; + onBrowserProcessExited?.call(detail); + } + break; + case 'onProcessInfosChanged': + if (onProcessInfosChanged != null) { + Map arguments = + call.arguments.cast(); + final detail = BrowserProcessInfosChangedDetail.fromMap(arguments)!; + onProcessInfosChanged?.call(detail); + } + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + Future isInterfaceSupported(WebViewInterface interface) async { + Map args = {}; + args.putIfAbsent('interface', () => interface.toNativeValue()); + return await channel?.invokeMethod('isInterfaceSupported', args) ?? + false; + } + + @override + Future> getProcessInfos() async { + Map args = {}; + final result = + await channel?.invokeMethod>('getProcessInfos', args); + return result + ?.map((e) => BrowserProcessInfo.fromMap(e.cast())) + .whereType() + .toList() ?? + []; + } + + @override + Future getFailureReportFolderPath() async { + Map args = {}; + return await channel?.invokeMethod( + 'getFailureReportFolderPath', args); + } + + @override + Future create( + {WebViewEnvironmentSettings? settings}) async { + final env = WindowsWebViewEnvironment( + WindowsWebViewEnvironmentCreationParams(settings: settings)); + + Map args = {}; + args.putIfAbsent('id', () => env.id); + args.putIfAbsent('settings', () => env.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; + } + + @override + Future getAvailableVersion({String? browserExecutableFolder}) async { + Map args = {}; + args.putIfAbsent('browserExecutableFolder', () => browserExecutableFolder); + return await _staticChannel.invokeMethod( + 'getAvailableVersion', args); + } + + @override + Future compareBrowserVersions( + {required String version1, required String version2}) async { + Map args = {}; + args.putIfAbsent('version1', () => version1); + args.putIfAbsent('version2', () => version2); + return await _staticChannel.invokeMethod( + 'compareBrowserVersions', args); + } + + @override + Future dispose() async { + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + } +} + +extension InternalWindowsWebViewEnvironment on WindowsWebViewEnvironment { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/pubspec.yaml b/flutter_inappwebview_windows/pubspec.yaml new file mode 100644 index 000000000..0ebc13a87 --- /dev/null +++ b/flutter_inappwebview_windows/pubspec.yaml @@ -0,0 +1,81 @@ +name: flutter_inappwebview_windows +description: Windows implementation of the flutter_inappwebview plugin. +version: 0.7.0-beta.3 +homepage: https://inappwebview.dev/ +repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_windows +issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +funding: + - https://inappwebview.dev/donate/ +topics: + - html + - webview + - webview-flutter + - inappwebview + - browser + +environment: + sdk: ^3.5.0 + flutter: ">=3.24.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: ^4.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: + platforms: + windows: + pluginClass: FlutterInappwebviewWindowsPluginCApi + dartPluginClass: WindowsInAppWebViewPlatform + + # 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/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # 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/custom-fonts/#from-packages diff --git a/flutter_inappwebview_windows/test/flutter_inappwebview_windows_test.dart b/flutter_inappwebview_windows/test/flutter_inappwebview_windows_test.dart new file mode 100644 index 000000000..e69de29bb diff --git a/flutter_inappwebview_windows/windows/.gitignore b/flutter_inappwebview_windows/windows/.gitignore new file mode 100644 index 000000000..d87ced66e --- /dev/null +++ b/flutter_inappwebview_windows/windows/.gitignore @@ -0,0 +1,19 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +packages/ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/CMakeLists.txt b/flutter_inappwebview_windows/windows/CMakeLists.txt new file mode 100644 index 000000000..666fbbe08 --- /dev/null +++ b/flutter_inappwebview_windows/windows/CMakeLists.txt @@ -0,0 +1,314 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. 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.14) + +set(WIL_VERSION "1.0.231216.1") +set(WEBVIEW_VERSION "1.0.2849.39") +set(NLOHMANN_JSON_VERSION "3.11.2") +set(CPP_WINRT_VERSION "2.0.240405.15") + +message(VERBOSE "CMake system version is ${CMAKE_SYSTEM_VERSION} (using SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})") + +# Project-level configuration. +set(PROJECT_NAME "flutter_inappwebview_windows") +project(${PROJECT_NAME} LANGUAGES CXX) + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "flutter_inappwebview_windows_plugin") + +find_program(NUGET nuget) +if(NOT NUGET) + message(NOTICE "Nuget is not installed! The flutter_inappwebview_windows plugin requires it. Check https://inappwebview.dev/docs/intro#setup-windows") +endif() + +add_custom_target(${PROJECT_NAME}_DEPS ALL) +add_custom_command( + TARGET ${PROJECT_NAME}_DEPS PRE_BUILD + COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install Microsoft.Windows.CppWinRT -Version ${CPP_WINRT_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install Microsoft.Web.WebView2 -Version ${WEBVIEW_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install nlohmann.json -Version ${NLOHMANN_JSON_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + DEPENDS ${NUGET} +) + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "flutter_inappwebview_windows_plugin.cpp" + "flutter_inappwebview_windows_plugin.h" + "utils/log.h" + "utils/defer.h" + "utils/timer.h" + "utils/strconv.h" + "utils/map.h" + "utils/vector.h" + "utils/string.h" + "utils/util.h" + "utils/flutter.h" + "utils/base64.cpp" + "utils/base64.h" + "utils/uri.h" + "utils/uuid.h" + "types/channel_delegate.cpp" + "types/channel_delegate.h" + "types/base_callback_result.h" + "types/url_request.cpp" + "types/url_request.h" + "types/navigation_action.cpp" + "types/navigation_action.h" + "types/web_resource_error.cpp" + "types/web_resource_error.h" + "types/web_resource_request.cpp" + "types/web_resource_request.h" + "types/web_resource_response.cpp" + "types/web_resource_response.h" + "types/web_history.cpp" + "types/web_history.h" + "types/web_history_item.cpp" + "types/web_history_item.h" + "types/content_world.cpp" + "types/content_world.h" + "types/user_script.cpp" + "types/user_script.h" + "types/plugin_script.cpp" + "types/plugin_script.h" + "types/size_2d.cpp" + "types/size_2d.h" + "types/rect.cpp" + "types/rect.h" + "types/callbacks_complete.h" + "types/screenshot_configuration.cpp" + "types/screenshot_configuration.h" + "types/create_window_action.cpp" + "types/create_window_action.h" + "types/new_window_requested_args.cpp" + "types/new_window_requested_args.h" + "types/window_features.cpp" + "types/window_features.h" + "types/ssl_certificate.cpp" + "types/ssl_certificate.h" + "types/permission_response.cpp" + "types/permission_response.h" + "types/custom_scheme_response.cpp" + "types/custom_scheme_response.h" + "types/custom_scheme_registration.cpp" + "types/custom_scheme_registration.h" + "types/javascript_handler_function_data.cpp" + "types/javascript_handler_function_data.h" + "types/ssl_error.cpp" + "types/ssl_error.h" + "types/url_credential.cpp" + "types/url_credential.h" + "types/url_protection_space.cpp" + "types/url_protection_space.h" + "types/url_authentication_challenge.cpp" + "types/url_authentication_challenge.h" + "types/http_authentication_challenge.cpp" + "types/http_authentication_challenge.h" + "types/http_auth_response.cpp" + "types/http_auth_response.h" + "types/client_cert_challenge.cpp" + "types/client_cert_challenge.h" + "types/client_cert_response.cpp" + "types/client_cert_response.h" + "types/server_trust_challenge.cpp" + "types/server_trust_challenge.h" + "types/server_trust_auth_response.cpp" + "types/server_trust_auth_response.h" + "types/security_origin.cpp" + "types/security_origin.h" + "types/frame_info.cpp" + "types/frame_info.h" + "types/process_failed_detail.cpp" + "types/process_failed_detail.h" + "types/render_process_gone_detail.cpp" + "types/render_process_gone_detail.h" + "types/download_start_request.cpp" + "types/download_start_request.h" + "types/download_start_response.cpp" + "types/download_start_response.h" + "types/browser_process_exited_detail.cpp" + "types/browser_process_exited_detail.h" + "types/browser_process_info.cpp" + "types/browser_process_info.h" + "types/browser_process_infos_changed_detail.cpp" + "types/browser_process_infos_changed_detail.h" + "types/physical_key_status.cpp" + "types/physical_key_status.h" + "types/accelerator_key_pressed_detail.cpp" + "types/accelerator_key_pressed_detail.h" + "custom_platform_view/custom_platform_view.cc" + "custom_platform_view/custom_platform_view.h" + "custom_platform_view/texture_bridge.cc" + "custom_platform_view/texture_bridge.h" + "custom_platform_view/graphics_context.cc" + "custom_platform_view/graphics_context.h" + "custom_platform_view/util/direct3d11.interop.cc" + "custom_platform_view/util/direct3d11.interop.h" + "custom_platform_view/util/rohelper.cc" + "custom_platform_view/util/rohelper.h" + "custom_platform_view/util/string_converter.cc" + "custom_platform_view/util/string_converter.h" + "custom_platform_view/util/swizzle.h" + "plugin_scripts_js/plugin_scripts_util.h" + "plugin_scripts_js/javascript_bridge_js.h" + "webview_environment/webview_environment_settings.cpp" + "webview_environment/webview_environment_settings.h" + "webview_environment/webview_environment.cpp" + "webview_environment/webview_environment.h" + "webview_environment/webview_environment_manager.cpp" + "webview_environment/webview_environment_manager.h" + "webview_environment/webview_environment_channel_delegate.cpp" + "webview_environment/webview_environment_channel_delegate.h" + "in_app_webview/user_content_controller.cpp" + "in_app_webview/user_content_controller.h" + "in_app_webview/in_app_webview_settings.cpp" + "in_app_webview/in_app_webview_settings.h" + "in_app_webview/in_app_webview.cpp" + "in_app_webview/in_app_webview.h" + "in_app_webview/in_app_webview_manager.cpp" + "in_app_webview/in_app_webview_manager.h" + "in_app_webview/webview_channel_delegate.cpp" + "in_app_webview/webview_channel_delegate.h" + "headless_in_app_webview/headless_in_app_webview.cpp" + "headless_in_app_webview/headless_in_app_webview.h" + "headless_in_app_webview/headless_in_app_webview_manager.cpp" + "headless_in_app_webview/headless_in_app_webview_manager.h" + "headless_in_app_webview/headless_webview_channel_delegate.cpp" + "headless_in_app_webview/headless_webview_channel_delegate.h" + "in_app_browser/in_app_browser_settings.cpp" + "in_app_browser/in_app_browser_settings.h" + "in_app_browser/in_app_browser_manager.cpp" + "in_app_browser/in_app_browser_manager.h" + "in_app_browser/in_app_browser.cpp" + "in_app_browser/in_app_browser.h" + "in_app_browser/in_app_browser_channel_delegate.cpp" + "in_app_browser/in_app_browser_channel_delegate.h" + "cookie_manager.cpp" + "cookie_manager.h" + "platform_util.cpp" + "platform_util.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h" + "flutter_inappwebview_windows_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +if(NOT FLUTTER_WEBVIEW_WINDOWS_USE_TEXTURE_FALLBACK) + message(STATUS "Building with D3D texture support.") + target_compile_definitions("${PLUGIN_NAME}" PRIVATE + HAVE_FLUTTER_D3D_TEXTURE + ) + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_gpu.cc" + "custom_platform_view/texture_bridge_gpu.h" + ) +else() + message(STATUS "Building with fallback PixelBuffer texture.") + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_fallback.cc" + "custom_platform_view/texture_bridge_fallback.h" + "custom_platform_view/util/cpuid/cpuinfo.cc" + "custom_platform_view/util/cpuid/cpuinfo.h" + ) + # Enable AVX2 for pixel buffer conversions + if(MSVC) + target_compile_options(${PLUGIN_NAME} PRIVATE "/arch:AVX2") + endif() +endif() + +# 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}) +# +# IMPORTANT: The apply_standard_settings function is not used here because it +# is causing the plugin to fail to compile because of the usage of /WX flag. +# So, creating here a custom function to apply the standard settings without the /WX flag. +# Also, added /bigobj flag. +function(FLUTTER_INAPPWEBVIEW_WINDOWS_APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_options(${TARGET} PRIVATE /bigobj) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() +flutter_inappwebview_windows_apply_standard_settings(${PLUGIN_NAME}) + +target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/Microsoft.Web.WebView2.targets) +target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary/build/native/Microsoft.Windows.ImplementationLibrary.targets) +target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/nlohmann.json/build/native/nlohmann.json.targets) + +# 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) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flutter_inappwebview_windows_bundled_libraries + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example, or +# from Visual Studio after opening the generated solution file. + +# 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}) +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 +) +# 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 C API is not very useful for unit testing, so build the sources +# directly into the test binary rather than using the DLL. +add_executable(${TEST_RUNNER} + test/flutter_inappwebview_windows_plugin_test.cpp + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) +# flutter_wrapper_plugin has link dependencies on the Flutter DLL. +add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${FLUTTER_LIBRARY}" $ +) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) +endif() diff --git a/flutter_inappwebview_windows/windows/cookie_manager.cpp b/flutter_inappwebview_windows/windows/cookie_manager.cpp new file mode 100644 index 000000000..2c1026143 --- /dev/null +++ b/flutter_inappwebview_windows/windows/cookie_manager.cpp @@ -0,0 +1,559 @@ +#include + +#include "cookie_manager.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 "types/callbacks_complete.h" +#include "utils/flutter.h" +#include "utils/log.h" +#include "utils/timer.h" + + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + CookieManager::CookieManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), CookieManager::METHOD_CHANNEL_NAME_PREFIX) + {} + + void CookieManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + auto webViewEnvironmentId = get_optional_fl_map_value(arguments, "webViewEnvironmentId"); + + auto webViewEnvironment = plugin && webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + InAppWebView* webView = nullptr; + auto webViewIdInt = get_optional_fl_map_value(arguments, "webViewId"); + auto webViewIdString = !webViewIdInt.has_value() ? get_optional_fl_map_value(arguments, "webViewId") : std::optional{}; + if (webViewIdInt.has_value() && plugin->inAppWebViewManager && map_contains(plugin->inAppWebViewManager->webViews, (uint64_t)webViewIdInt.value())) { + webView = plugin->inAppWebViewManager->webViews.at(webViewIdInt.value())->view.get(); + } + else if (webViewIdString.has_value()) { + if (plugin->inAppWebViewManager && map_contains(plugin->inAppWebViewManager->keepAliveWebViews, webViewIdString.value())) { + webView = plugin->inAppWebViewManager->keepAliveWebViews.at(webViewIdString.value())->view.get(); + } + else if (plugin->headlessInAppWebViewManager && map_contains(plugin->headlessInAppWebViewManager->webViews, webViewIdString.value())) { + webView = plugin->headlessInAppWebViewManager->webViews.at(webViewIdString.value())->webView.get(); + } + else if (plugin->inAppBrowserManager && map_contains(plugin->inAppBrowserManager->browsers, webViewIdString.value())) { + webView = plugin->inAppBrowserManager->browsers.at(webViewIdString.value())->webView.get(); + } + } + + auto result_ = std::shared_ptr>(std::move(result)); + auto callback = [this, result_, methodName, arguments, webView](WebViewEnvironment* webViewEnvironment) + { + if (!webViewEnvironment) { + result_->Error("0", "Cannot obtain the WebViewEnvironment!"); + return; + } + + if (string_equals(methodName, "setCookie")) { + setCookie(webViewEnvironment, webView, arguments, [result_](const bool& created) + { + result_->Success(created); + }); + } + else if (string_equals(methodName, "getCookie")) { + auto url = get_fl_map_value(arguments, "url"); + auto name = get_fl_map_value(arguments, "name"); + getCookie(webViewEnvironment, webView, url, name, [result_](const flutter::EncodableValue& cookie) + { + result_->Success(cookie); + }); + } + else if (string_equals(methodName, "getCookies")) { + auto url = get_fl_map_value(arguments, "url"); + getCookies(webViewEnvironment, webView, url, [result_](const flutter::EncodableList& cookies) + { + result_->Success(cookies); + }); + } + else if (string_equals(methodName, "deleteCookie")) { + auto url = get_fl_map_value(arguments, "url"); + auto name = get_fl_map_value(arguments, "name"); + auto path = get_fl_map_value(arguments, "path"); + auto domain = get_optional_fl_map_value(arguments, "domain"); + deleteCookie(webViewEnvironment, webView, url, name, path, domain, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else if (string_equals(methodName, "deleteCookies")) { + auto url = get_fl_map_value(arguments, "url"); + auto path = get_fl_map_value(arguments, "path"); + auto domain = get_optional_fl_map_value(arguments, "domain"); + deleteCookies(webViewEnvironment, webView, url, path, domain, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else if (string_equals(methodName, "deleteAllCookies")) { + deleteAllCookies(webViewEnvironment, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else { + result_->NotImplemented(); + } + }; + + if (webViewEnvironment) { + callback(webViewEnvironment); + } + else { + plugin->webViewEnvironmentManager->createOrGetDefaultWebViewEnvironment([callback](WebViewEnvironment* webViewEnvironment) + { + callback(webViewEnvironment); + }); + } + } + + void CookieManager::setCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const flutter::EncodableMap& map, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + auto url = get_fl_map_value(map, "url"); + auto name = get_fl_map_value(map, "name"); + auto value = get_fl_map_value(map, "value"); + auto path = get_fl_map_value(map, "path"); + auto domain = get_optional_fl_map_value(map, "domain"); + auto expiresDate = get_optional_fl_map_value(map, "expiresDate"); + auto maxAge = get_optional_fl_map_value(map, "maxAge"); + auto isSecure = get_optional_fl_map_value(map, "isSecure"); + auto isHttpOnly = get_optional_fl_map_value(map, "isHttpOnly"); + auto sameSite = get_optional_fl_map_value(map, "sameSite"); + + nlohmann::json parameters = { + {"url", url}, + {"name", name}, + {"value", value}, + {"path", path} + }; + if (domain.has_value()) { + parameters["domain"] = domain.value(); + } + if (expiresDate.has_value()) { + parameters["expires"] = expiresDate.value() / 1000; + } + if (maxAge.has_value()) { + // time(NULL) represents the current unix timestamp in seconds + parameters["expires"] = time(NULL) + maxAge.value(); + } + if (isSecure.has_value()) { + parameters["secure"] = isSecure.value(); + } + if (isHttpOnly.has_value()) { + parameters["httpOnly"] = isHttpOnly.value(); + } + if (sameSite.has_value()) { + parameters["sameSite"] = sameSite.value(); + } + + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) + { + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.setCookie", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([this, callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); + } + } + + void CookieManager::getCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(make_fl_value()); + } + return; + } + + nlohmann::json parameters = { + {"urls", std::vector{url}} + }; + + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) + { + if (!webView) { + completionHandler(make_fl_value()); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + auto cookieName = jsonCookie["name"].get(); + if (string_equals(name, cookieName)) { + completionHandler(flutter::EncodableMap{ + {"name", cookieName}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + return S_OK; + } + } + } + if (completionHandler) { + completionHandler(make_fl_value()); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(make_fl_value()); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); + } + } + + void CookieManager::getCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler({}); + } + return; + } + + nlohmann::json parameters = { + {"urls", std::vector{url}} + }; + + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) + { + if (!webView) { + completionHandler({}); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::vector cookies = {}; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + cookies.push_back(flutter::EncodableMap{ + {"name", jsonCookie["name"].get()}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + } + } + + if (completionHandler) { + completionHandler(cookies); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler({}); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); + } + } + + void CookieManager::deleteCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + nlohmann::json parameters = { + {"url", url}, + {"name", name}, + {"path", path} + }; + if (domain.has_value()) { + parameters["domain"] = domain.value(); + } + + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) + { + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.deleteCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); + } + } + + void CookieManager::deleteCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + getCookies(webViewEnvironment, inAppWebView, url, [this, webViewEnvironment, inAppWebView, url, path, domain, completionHandler](const flutter::EncodableList& cookies) + { + auto callbacksComplete = std::make_shared>( + [completionHandler](const std::vector& values) + { + if (completionHandler) { + completionHandler(true); + } + }); + + for (auto& cookie : cookies) { + auto cookieMap = std::get(cookie); + auto name = get_fl_map_value(cookieMap, "name"); + deleteCookie(webViewEnvironment, inAppWebView, url, name, path, domain, [callbacksComplete](const bool& deleted) + { + callbacksComplete->addValue(deleted); + }); + } + }); + } + + void CookieManager::deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) + { + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.clearBrowserCookies", L"{}", Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); + } + + CookieManager::~CookieManager() + { + debugLog("dealloc CookieManager"); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/cookie_manager.h b/flutter_inappwebview_windows/windows/cookie_manager.h new file mode 100644 index 000000000..db0561247 --- /dev/null +++ b/flutter_inappwebview_windows/windows/cookie_manager.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ + +#include +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" +#include "in_app_webview/in_app_webview.h" +#include "types/channel_delegate.h" +#include "webview_environment/webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + class CookieManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"; + + const FlutterInappwebviewWindowsPlugin* plugin; + + CookieManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~CookieManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void setCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const flutter::EncodableMap& map, std::function completionHandler) const; + void getCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, std::function completionHandler) const; + void getCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, std::function completionHandler) const; + void deleteCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler); + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc new file mode 100644 index 000000000..f990fcc44 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc @@ -0,0 +1,338 @@ +#include "../utils/log.h" +#include "custom_platform_view.h" + +#include +#include + +#ifdef HAVE_FLUTTER_D3D_TEXTURE +#include "texture_bridge_gpu.h" +#else +#include "texture_bridge_fallback.h" +#endif + +namespace flutter_inappwebview_plugin +{ + constexpr auto kErrorInvalidArgs = "invalidArguments"; + + constexpr auto kMethodSetSize = "setSize"; + constexpr auto kMethodSetPosition = "setPosition"; + constexpr auto kMethodSetCursorPos = "setCursorPos"; + constexpr auto kMethodSetPointerUpdate = "setPointerUpdate"; + constexpr auto kMethodSetPointerButton = "setPointerButton"; + constexpr auto kMethodSetScrollDelta = "setScrollDelta"; + constexpr auto kMethodSetFpsLimit = "setFpsLimit"; + + constexpr auto kEventType = "type"; + constexpr auto kEventValue = "value"; + + static const std::optional> GetPointFromArgs( + const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 2) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + if (!x || !y) { + return std::nullopt; + } + return std::make_pair(*x, *y); + } + + static const std::optional> + GetPointAndScaleFactorFromArgs(const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 3) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + const auto z = std::get_if(&(*list)[2]); + if (!x || !y || !z) { + return std::nullopt; + } + return std::make_tuple(*x, *y, *z); + } + + static const std::string& GetCursorName(const HCURSOR cursor) + { + // The cursor names correspond to the Flutter Engine names: + // in shell/platform/windows/flutter_window_win32.cc + static const std::string kDefaultCursorName = "basic"; + static const std::pair mappings[] = { + {"allScroll", IDC_SIZEALL}, + {kDefaultCursorName, IDC_ARROW}, + {"click", IDC_HAND}, + {"forbidden", IDC_NO}, + {"help", IDC_HELP}, + {"move", IDC_SIZEALL}, + {"none", nullptr}, + {"noDrop", IDC_NO}, + {"precise", IDC_CROSS}, + {"progress", IDC_APPSTARTING}, + {"text", IDC_IBEAM}, + {"resizeColumn", IDC_SIZEWE}, + {"resizeDown", IDC_SIZENS}, + {"resizeDownLeft", IDC_SIZENESW}, + {"resizeDownRight", IDC_SIZENWSE}, + {"resizeLeft", IDC_SIZEWE}, + {"resizeLeftRight", IDC_SIZEWE}, + {"resizeRight", IDC_SIZEWE}, + {"resizeRow", IDC_SIZENS}, + {"resizeUp", IDC_SIZENS}, + {"resizeUpDown", IDC_SIZENS}, + {"resizeUpLeft", IDC_SIZENWSE}, + {"resizeUpRight", IDC_SIZENESW}, + {"resizeUpLeftDownRight", IDC_SIZENWSE}, + {"resizeUpRightDownLeft", IDC_SIZENESW}, + {"wait", IDC_WAIT}, + }; + + static std::map cursors; + static bool initialized = false; + + if (!initialized) { + initialized = true; + for (const auto& pair : mappings) { + HCURSOR cursor_handle = LoadCursor(nullptr, pair.second); + if (cursor_handle) { + cursors[cursor_handle] = pair.first; + } + } + } + + const auto it = cursors.find(cursor); + if (it != cursors.end()) { + return it->second; + } + return kDefaultCursorName; + } + + CustomPlatformView::CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::shared_ptr webView) + : hwnd_(hwnd), view(std::move(webView)), texture_registrar_(texture_registrar) + { +#ifdef HAVE_FLUTTER_D3D_TEXTURE + texture_bridge_ = + std::make_unique(graphics_context, view->surface()); + + flutter_texture_ = + std::make_unique(flutter::GpuSurfaceTexture( + kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, + [bridge = static_cast(texture_bridge_.get())]( + size_t width, + size_t height) -> const FlutterDesktopGpuSurfaceDescriptor* + { + return bridge->GetSurfaceDescriptor(width, height); + })); +#else + texture_bridge_ = std::make_unique( + graphics_context, webview_->surface()); + + flutter_texture_ = + std::make_unique(flutter::PixelBufferTexture( + [bridge = static_cast(texture_bridge_.get())]( + size_t width, size_t height) -> const FlutterDesktopPixelBuffer* + { + return bridge->CopyPixelBuffer(width, height); + })); +#endif + + texture_id_ = texture_registrar->RegisterTexture(flutter_texture_.get()); + texture_bridge_->SetOnFrameAvailable( + [this]() { texture_registrar_->MarkTextureFrameAvailable(texture_id_); }); + // texture_bridge_->SetOnSurfaceSizeChanged([this](Size size) { + // view->SetSurfaceSize(size.width, size.height); + //}); + + const auto method_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_); + method_channel_ = + std::make_unique>( + messenger, method_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + method_channel_->SetMethodCallHandler([this](const auto& call, auto result) + { + HandleMethodCall(call, std::move(result)); + }); + + const auto event_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_) + "_events"; + event_channel_ = + std::make_unique>( + messenger, event_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [this](const flutter::EncodableValue* arguments, + std::unique_ptr>&& + events) + { + event_sink_ = std::move(events); + RegisterEventHandlers(); + return nullptr; + }, + [this](const flutter::EncodableValue* arguments) + { + return nullptr; + }); + + event_channel_->SetStreamHandler(std::move(handler)); + } + + void CustomPlatformView::UnregisterMethodCallHandler() const + { + if (method_channel_) { + method_channel_->SetMethodCallHandler(nullptr); + if (view && view->channelDelegate) { + view->channelDelegate->UnregisterMethodCallHandler(); + } + } + } + + CustomPlatformView::~CustomPlatformView() + { + debugLog("dealloc CustomPlatformView"); + event_sink_ = nullptr; + texture_registrar_->UnregisterTexture(texture_id_, nullptr); + } + + void CustomPlatformView::RegisterEventHandlers() + { + if (!view) { + return; + } + + view->onSurfaceSizeChanged([this](size_t width, size_t height) + { + texture_bridge_->NotifySurfaceSizeChanged(); + }); + + view->onCursorChanged([this](const HCURSOR cursor) + { + const auto& name = GetCursorName(cursor); + const auto event = flutter::EncodableValue( + flutter::EncodableMap { { + flutter::EncodableValue(kEventType), + flutter::EncodableValue("cursorChanged") + }, + { flutter::EncodableValue(kEventValue), name }}); + EmitEvent(event); + }); + } + + void CustomPlatformView::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + const auto& method_name = method_call.method_name(); + + // setCursorPos: [double x, double y] + if (method_name.compare(kMethodSetCursorPos) == 0) { + const auto point = GetPointFromArgs(method_call.arguments()); + if (point && view) { + view->setCursorPos(point->first, point->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerUpdate: + // [int pointer, int event, double x, double y, double size, double pressure] + if (method_name.compare(kMethodSetPointerUpdate) == 0) { + const flutter::EncodableList* list = + std::get_if(method_call.arguments()); + if (!list || list->size() != 6) { + return result->Error(kErrorInvalidArgs); + } + + const auto pointer = std::get_if(&(*list)[0]); + const auto event = std::get_if(&(*list)[1]); + const auto x = std::get_if(&(*list)[2]); + const auto y = std::get_if(&(*list)[3]); + const auto size = std::get_if(&(*list)[4]); + const auto pressure = std::get_if(&(*list)[5]); + + if (pointer && event && x && y && size && pressure && view) { + view->setPointerUpdate(*pointer, + static_cast(*event), + *x, *y, *size, *pressure); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setScrollDelta: [double dx, double dy] + if (method_name.compare(kMethodSetScrollDelta) == 0) { + const auto delta = GetPointFromArgs(method_call.arguments()); + if (delta && view) { + view->setScrollDelta(delta->first, delta->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerButton: {"button": int, "isDown": bool} + if (method_name.compare(kMethodSetPointerButton) == 0) { + const auto& map = std::get(*method_call.arguments()); + + const auto kind = map.find(flutter::EncodableValue("kind")); + const auto button = map.find(flutter::EncodableValue("button")); + if (kind != map.end() && button != map.end()) { + const auto kindValue = std::get_if(&kind->second); + const auto buttonValue = std::get_if(&button->second); + if (kindValue && buttonValue && view) { + view->setPointerButtonState( + static_cast(*kindValue), + static_cast(*buttonValue)); + return result->Success(); + } + } + return result->Error(kErrorInvalidArgs); + } + + // setSize: [double width, double height, double scale_factor] + if (method_name.compare(kMethodSetSize) == 0) { + auto size = GetPointAndScaleFactorFromArgs(method_call.arguments()); + if (size && view) { + const auto [width, height, scale_factor] = size.value(); + + view->setSurfaceSize(static_cast(width), + static_cast(height), + static_cast(scale_factor)); + + texture_bridge_->Start(); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + else if (method_name.compare(kMethodSetPosition) == 0) { + auto position = GetPointAndScaleFactorFromArgs(method_call.arguments()); + if (position && view) { + const auto [x, y, scale_factor] = position.value(); + + view->setPosition(static_cast(x), + static_cast(y), + static_cast(scale_factor)); + + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + else if (method_name.compare(kMethodSetFpsLimit) == 0) { + if (const auto value = std::get_if(method_call.arguments())) { + texture_bridge_->SetFpsLimit(*value == 0 ? std::nullopt + : std::make_optional(*value)); + return result->Success(); + } + } + + result->NotImplemented(); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h new file mode 100644 index 000000000..65db5476a --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "../in_app_webview/in_app_webview.h" +#include "graphics_context.h" +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class CustomPlatformView { + public: + static inline const wchar_t* CLASS_NAME = L"CustomPlatformView"; + + std::shared_ptr view; + + CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::shared_ptr webView); + ~CustomPlatformView(); + + TextureBridge* texture_bridge() const { return texture_bridge_.get(); } + + int64_t texture_id() const { return texture_id_; } + + void UnregisterMethodCallHandler() const; + private: + HWND hwnd_; + std::unique_ptr flutter_texture_; + std::unique_ptr texture_bridge_; + std::unique_ptr> event_sink_; + std::unique_ptr> + event_channel_; + std::unique_ptr> + method_channel_; + + flutter::TextureRegistrar* texture_registrar_; + int64_t texture_id_; + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + void RegisterEventHandlers(); + + template + void EmitEvent(const T& value) + { + if (event_sink_) { + event_sink_->Success(value); + } + } + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc new file mode 100644 index 000000000..2bbc1e748 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc @@ -0,0 +1,158 @@ +#include "graphics_context.h" + +#include "util/d3dutil.h" +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + GraphicsContext::GraphicsContext(rx::RoHelper* rohelper) : rohelper_(rohelper) + { + device_ = CreateD3DDevice(); + if (!device_) { + return; + } + + device_->GetImmediateContext(device_context_.put()); + if (FAILED(CreateDirect3D11DeviceFromDXGIDevice( + device_.try_as().get(), + (IInspectable**)device_winrt_.put()))) { + return; + } + + valid_ = true; + } + + winrt::com_ptr + GraphicsContext::CreateCompositor() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_UI_Composition_Compositor, &className, + &classNameHeader))) { + return nullptr; + } + + winrt::com_ptr af; + if (FAILED(rohelper_->GetActivationFactory( + className, __uuidof(IActivationFactory), af.put_void()))) { + return nullptr; + } + + winrt::com_ptr compositor; + if (FAILED(af->ActivateInstance( + reinterpret_cast(compositor.put())))) { + return nullptr; + } + + return compositor; + } + + winrt::com_ptr + GraphicsContext::CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &className, + &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics* + capture_item_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics), + (void**)&capture_item_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_item; + if (FAILED( + capture_item_statics->CreateFromVisual(visual, capture_item.put()))) { + return nullptr; + } + + return capture_item; + } + + winrt::com_ptr + GraphicsContext::CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->Create(device, pixelFormat, + numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } + + winrt::com_ptr + GraphicsContext::CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics2* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics2), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->CreateFreeThreaded( + device, pixelFormat, numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h new file mode 100644 index 000000000..883bffe71 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/rohelper.h" + +namespace flutter_inappwebview_plugin +{ + class GraphicsContext { + public: + GraphicsContext(rx::RoHelper* rohelper); + + inline bool IsValid() const { return valid_; } + + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device() const + { + return device_winrt_.get(); + } + ID3D11Device* d3d_device() const { return device_.get(); } + ID3D11DeviceContext* d3d_device_context() const + { + return device_context_.get(); + } + + winrt::com_ptr CreateCompositor(); + + winrt::com_ptr + CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const; + + winrt::com_ptr + CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + winrt::com_ptr + CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + private: + bool valid_ = false; + rx::RoHelper* rohelper_; + winrt::com_ptr + device_winrt_; + winrt::com_ptr device_{ nullptr }; + winrt::com_ptr device_context_{ nullptr }; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc new file mode 100644 index 000000000..658df19fc --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc @@ -0,0 +1,189 @@ +#include "texture_bridge.h" + +#include + +#include +#include +#include +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + const int kNumBuffers = 1; + + TextureBridge::TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : graphics_context_(graphics_context) + { + capture_item_ = + graphics_context_->CreateGraphicsCaptureItemFromVisual(visual); + assert(capture_item_); + + capture_item_->add_Closed( + Microsoft::WRL::Callback>( + [](ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* item, + IInspectable* args) -> HRESULT + { + std::cerr << "Capture item was closed." << std::endl; + return S_OK; + }) + .Get(), + &on_closed_token_); + } + + TextureBridge::~TextureBridge() + { + const std::lock_guard lock(mutex_); + StopInternal(); + if (capture_item_) { + capture_item_->remove_Closed(on_closed_token_); + } + } + + bool TextureBridge::Start() + { + const std::lock_guard lock(mutex_); + if (is_running_ || !capture_item_) { + return false; + } + + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + + frame_pool_ = graphics_context_->CreateCaptureFramePool( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + assert(frame_pool_); + + frame_pool_->add_FrameArrived( + Microsoft::WRL::Callback>( + [this](ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* + pool, + IInspectable* args) -> HRESULT + { + OnFrameArrived(); + return S_OK; + }) + .Get(), + &on_frame_arrived_token_); + + if (FAILED(frame_pool_->CreateCaptureSession(capture_item_.get(), + capture_session_.put()))) { + std::cerr << "Creating capture session failed." << std::endl; + return false; + } + + if (SUCCEEDED(capture_session_->StartCapture())) { + is_running_ = true; + return true; + } + + return false; + } + + void TextureBridge::Stop() + { + const std::lock_guard lock(mutex_); + StopInternal(); + } + + void TextureBridge::StopInternal() + { + if (is_running_) { + is_running_ = false; + frame_pool_->remove_FrameArrived(on_frame_arrived_token_); + auto closable = + capture_session_.try_as(); + assert(closable); + closable->Close(); + capture_session_ = nullptr; + } + } + + void TextureBridge::OnFrameArrived() + { + const std::lock_guard lock(mutex_); + if (!is_running_) { + return; + } + + bool has_frame = false; + + winrt::com_ptr + frame; + auto hr = frame_pool_->TryGetNextFrame(frame.put()); + if (SUCCEEDED(hr) && frame) { + winrt::com_ptr< + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface> + frame_surface; + + if (SUCCEEDED(frame->get_Surface(frame_surface.put()))) { + last_frame_ = + TryGetDXGIInterfaceFromObject(frame_surface); + has_frame = !ShouldDropFrame(); + } + } + + if (needs_update_) { + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + frame_pool_->Recreate( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + needs_update_ = false; + } + + if (has_frame && frame_available_) { + frame_available_(); + } + } + + bool TextureBridge::ShouldDropFrame() + { + if (!frame_duration_.has_value()) { + return false; + } + auto now = std::chrono::high_resolution_clock::now(); + + bool should_drop_frame = false; + if (last_frame_timestamp_.has_value()) { + auto diff = std::chrono::duration_cast( + now - last_frame_timestamp_.value()); + should_drop_frame = diff < frame_duration_.value(); + } + + if (!should_drop_frame) { + last_frame_timestamp_ = now; + } + return should_drop_frame; + } + + void TextureBridge::NotifySurfaceSizeChanged() + { + const std::lock_guard lock(mutex_); + needs_update_ = true; + } + + void TextureBridge::SetFpsLimit(std::optional max_fps) + { + const std::lock_guard lock(mutex_); + auto value = max_fps.value_or(0); + if (value != 0) { + frame_duration_ = FrameDuration(1000.0 / value); + } + else { + frame_duration_.reset(); + last_frame_timestamp_.reset(); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h new file mode 100644 index 000000000..122c69388 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include "graphics_context.h" + +namespace flutter_inappwebview_plugin +{ + typedef struct { + size_t width; + size_t height; + } Size; + + class TextureBridge { + public: + typedef std::function FrameAvailableCallback; + typedef std::function SurfaceSizeChangedCallback; + typedef std::chrono::duration FrameDuration; + + TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + virtual ~TextureBridge(); + + bool Start(); + void Stop(); + + void SetOnFrameAvailable(FrameAvailableCallback callback) + { + frame_available_ = std::move(callback); + } + + void SetOnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surface_size_changed_ = std::move(callback); + } + + void NotifySurfaceSizeChanged(); + void SetFpsLimit(std::optional max_fps); + + protected: + bool is_running_ = false; + + const GraphicsContext* graphics_context_; + std::mutex mutex_; + std::optional frame_duration_ = std::nullopt; + + FrameAvailableCallback frame_available_; + SurfaceSizeChangedCallback surface_size_changed_; + std::atomic needs_update_ = false; + winrt::com_ptr last_frame_; + std::optional + last_frame_timestamp_; + + winrt::com_ptr + capture_item_; + winrt::com_ptr + frame_pool_; + winrt::com_ptr + capture_session_; + + EventRegistrationToken on_closed_token_ = {}; + EventRegistrationToken on_frame_arrived_token_ = {}; + + virtual void StopInternal(); + void OnFrameArrived(); + bool ShouldDropFrame(); + + // corresponds to DXGI_FORMAT_B8G8R8A8_UNORM + static constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX:: + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc new file mode 100644 index 000000000..6aa5d58d7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc @@ -0,0 +1,145 @@ +#include "texture_bridge_fallback.h" + +#include + +#include "util/direct3d11.interop.h" +#include "util/swizzle.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeFallback::TextureBridgeFallback( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + {} + + TextureBridgeFallback::~TextureBridgeFallback() + { + const std::lock_guard lock(buffer_mutex_); + } + + void TextureBridgeFallback::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + bool is_exact_size; + EnsureStagingTexture(width, height, is_exact_size); + + auto device_context = graphics_context_->d3d_device_context(); + auto staging_texture = staging_texture_.get(); + + if (is_exact_size) { + device_context->CopyResource(staging_texture, src_texture.get()); + } + else { + D3D11_BOX client_box; + client_box.top = 0; + client_box.left = 0; + client_box.right = width; + client_box.bottom = height; + client_box.front = 0; + client_box.back = 1; + device_context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0, + src_texture.get(), 0, &client_box); + } + + D3D11_MAPPED_SUBRESOURCE mappedResource; + if (!SUCCEEDED(device_context->Map(staging_texture, 0, D3D11_MAP_READ, 0, + &mappedResource))) { + return; + } + + { + const std::lock_guard lock(buffer_mutex_); + if (!pixel_buffer_ || pixel_buffer_->width != width || + pixel_buffer_->height != height) { + if (!pixel_buffer_) { + pixel_buffer_ = std::make_unique(); + pixel_buffer_->release_context = &buffer_mutex_; + // Gets invoked after the FlutterDesktopPixelBuffer's + // backing buffer has been uploaded. + pixel_buffer_->release_callback = [](void* opaque) + { + auto mutex = reinterpret_cast(opaque); + // Gets locked just before |CopyPixelBuffer| returns. + mutex->unlock(); + }; + } + pixel_buffer_->width = width; + pixel_buffer_->height = height; + const auto size = width * height * 4; + backing_pixel_buffer_.reset(new uint8_t[size]); + pixel_buffer_->buffer = backing_pixel_buffer_.get(); + } + + const auto src_pitch_in_pixels = mappedResource.RowPitch / 4; + RGBA_to_BGRA(reinterpret_cast(backing_pixel_buffer_.get()), + static_cast(mappedResource.pData), height, + src_pitch_in_pixels, width); + } + + device_context->Unmap(staging_texture, 0); + } + + void TextureBridgeFallback::EnsureStagingTexture(uint32_t width, + uint32_t height, + bool& is_exact_size) + { + // Only recreate an existing texture if it's too small. + if (!staging_texture_ || staging_texture_size_.width < width || + staging_texture_size_.height < height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = 0; + dstDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = 0; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_STAGING; + + staging_texture_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, staging_texture_.put()))) { + std::cerr << "Creating dst texture failed" << std::endl; + return; + } + + staging_texture_size_ = { width, height }; + } + + is_exact_size = staging_texture_size_.width == width && + staging_texture_size_.height == height; + } + + const FlutterDesktopPixelBuffer* TextureBridgeFallback::CopyPixelBuffer( + size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + auto buffer = pixel_buffer_.get(); + // Only lock the mutex if the buffer is not null + // (to ensure the release callback gets called) + if (buffer) { + // Gets unlocked in the FlutterDesktopPixelBuffer's release callback. + buffer_mutex_.lock(); + } + return buffer; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h new file mode 100644 index 000000000..ad07b77cd --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeFallback : public TextureBridge { + public: + TextureBridgeFallback(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + ~TextureBridgeFallback() override; + + const FlutterDesktopPixelBuffer* CopyPixelBuffer(size_t width, size_t height); + + private: + Size staging_texture_size_ = { 0, 0 }; + winrt::com_ptr staging_texture_{ nullptr }; + std::mutex buffer_mutex_; + std::unique_ptr backing_pixel_buffer_; + std::unique_ptr pixel_buffer_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureStagingTexture(uint32_t width, uint32_t height, + bool& is_exact_size); + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc new file mode 100644 index 000000000..c3bdee266 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc @@ -0,0 +1,108 @@ +#include "texture_bridge_gpu.h" + +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeGpu::TextureBridgeGpu( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + { + surface_descriptor_.struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor); + surface_descriptor_.format = + kFlutterDesktopPixelFormatNone; // no format required for DXGI surfaces + } + + void TextureBridgeGpu::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + EnsureSurface(width, height); + + auto device_context = graphics_context_->d3d_device_context(); + + device_context->CopyResource(surface_.get(), src_texture.get()); + device_context->Flush(); + } + + void TextureBridgeGpu::EnsureSurface(uint32_t width, uint32_t height) + { + if (!surface_ || surface_size_.width != width || + surface_size_.height != height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + dstDesc.CPUAccessFlags = 0; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_DEFAULT; + + surface_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, surface_.put()))) { + std::cerr << "Creating intermediate texture failed" << std::endl; + return; + } + + HANDLE shared_handle; + surface_.try_as(dxgi_surface_); + assert(dxgi_surface_); + dxgi_surface_->GetSharedHandle(&shared_handle); + + surface_descriptor_.handle = shared_handle; + surface_descriptor_.width = surface_descriptor_.visible_width = width; + surface_descriptor_.height = surface_descriptor_.visible_height = height; + surface_descriptor_.release_context = surface_.get(); + surface_descriptor_.release_callback = [](void* release_context) + { + auto texture = reinterpret_cast(release_context); + texture->Release(); + }; + + surface_size_ = { width, height }; + } + } + + const FlutterDesktopGpuSurfaceDescriptor* + TextureBridgeGpu::GetSurfaceDescriptor(size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + if (surface_) { + // Gets released in the SurfaceDescriptor's release callback. + surface_->AddRef(); + } + + return &surface_descriptor_; + } + + void TextureBridgeGpu::StopInternal() + { + TextureBridge::StopInternal(); + + // For some reason, the destination surface needs to be recreated upon + // resuming. Force |EnsureSurface| to create a new one by resetting it here. + surface_ = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h new file mode 100644 index 000000000..8b29a3216 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeGpu : public TextureBridge { + public: + TextureBridgeGpu(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + + const FlutterDesktopGpuSurfaceDescriptor* GetSurfaceDescriptor(size_t width, + size_t height); + + protected: + void StopInternal() override; + + private: + FlutterDesktopGpuSurfaceDescriptor surface_descriptor_ = {}; + Size surface_size_ = { 0, 0 }; + winrt::com_ptr surface_{ nullptr }; + winrt::com_ptr dxgi_surface_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureSurface(uint32_t width, uint32_t height); + }; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h new file mode 100644 index 000000000..9b9eb0d30 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace util { + + winrt::com_ptr TryCreateDesktopWindowTarget( + const winrt::com_ptr& + compositor, + HWND window) + { + namespace abi = ABI::Windows::UI::Composition::Desktop; + auto interop = compositor.try_as(); + + winrt::com_ptr target; + interop->CreateDesktopWindowTarget(window, true, target.put()); + return target; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc new file mode 100644 index 000000000..acee3437d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc @@ -0,0 +1,80 @@ +#include "cpuinfo.h" + +#include "detail/cpuinfo_impl.h" + +#if defined(_MSC_VER) && (defined(__x86_64__) || defined(_M_X64)) +#include "detail/init_msvc_x86.h" +#else +#include "detail/init_unknown.hpp" +#endif + +namespace cpuid { + +cpuinfo::cpuinfo() : impl_(new impl) { init_cpuinfo(*impl_); } + +cpuinfo::~cpuinfo() {} + +// x86 member functions +bool cpuinfo::has_fpu() const { return impl_->m_has_fpu; } + +bool cpuinfo::has_mmx() const { return impl_->m_has_mmx; } + +bool cpuinfo::has_sse() const { return impl_->m_has_sse; } + +bool cpuinfo::has_sse2() const { return impl_->m_has_sse2; } + +bool cpuinfo::has_sse3() const { return impl_->m_has_sse3; } + +bool cpuinfo::has_ssse3() const { return impl_->m_has_ssse3; } + +bool cpuinfo::has_sse4_1() const { return impl_->m_has_sse4_1; } + +bool cpuinfo::has_sse4_2() const { return impl_->m_has_sse4_2; } + +bool cpuinfo::has_pclmulqdq() const { return impl_->m_has_pclmulqdq; } + +bool cpuinfo::has_avx() const { return impl_->m_has_avx; } + +bool cpuinfo::has_avx2() const { return impl_->m_has_avx2; } + +bool cpuinfo::has_avx512_f() const { return impl_->m_has_avx512_f; } + +bool cpuinfo::has_avx512_dq() const { return impl_->m_has_avx512_dq; } + +bool cpuinfo::has_avx512_ifma() const { return impl_->m_has_avx512_ifma; } + +bool cpuinfo::has_avx512_pf() const { return impl_->m_has_avx512_pf; } + +bool cpuinfo::has_avx512_er() const { return impl_->m_has_avx512_er; } + +bool cpuinfo::has_avx512_cd() const { return impl_->m_has_avx512_cd; } + +bool cpuinfo::has_avx512_bw() const { return impl_->m_has_avx512_bw; } + +bool cpuinfo::has_avx512_vl() const { return impl_->m_has_avx512_vl; } + +bool cpuinfo::has_avx512_vbmi() const { return impl_->m_has_avx512_vbmi; } + +bool cpuinfo::has_avx512_vbmi2() const { return impl_->m_has_avx512_vbmi2; } + +bool cpuinfo::has_avx512_vnni() const { return impl_->m_has_avx512_vnni; } + +bool cpuinfo::has_avx512_bitalg() const { return impl_->m_has_avx512_bitalg; } + +bool cpuinfo::has_avx512_vpopcntdq() const { + return impl_->m_has_avx512_vpopcntdq; +} + +bool cpuinfo::has_avx512_4vnniw() const { return impl_->m_has_avx512_4vnniw; } + +bool cpuinfo::has_avx512_4fmaps() const { return impl_->m_has_avx512_4fmaps; } + +bool cpuinfo::has_avx512_vp2intersect() const { + return impl_->m_has_avx512_vp2intersect; +} + +bool cpuinfo::has_f16c() const { return impl_->m_has_f16c; } + +// ARM member functions +bool cpuinfo::has_neon() const { return impl_->m_has_neon; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h new file mode 100644 index 000000000..0cee35982 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +namespace cpuid { + +class cpuinfo { + public: + struct impl; + + cpuinfo(); + ~cpuinfo(); + + // Has X87 FPU + bool has_fpu() const; + + // Return true if the CPU supports MMX + bool has_mmx() const; + + // Return true if the CPU supports SSE + bool has_sse() const; + + // Return true if the CPU supports SSE2 + bool has_sse2() const; + + // Return true if the CPU supports SSE3 + bool has_sse3() const; + + // Return true if the CPU supports SSSE3 + bool has_ssse3() const; + + // Return true if the CPU supports SSE 4.1 + bool has_sse4_1() const; + + // Return true if the CPU supports SSE 4.2 + bool has_sse4_2() const; + + // Return true if the CPU supports pclmulqdq + bool has_pclmulqdq() const; + + // Return true if the CPU supports AVX + bool has_avx() const; + + // Return true if the CPU supports AVX2 + bool has_avx2() const; + + // Return true if the CPU supports AVX512F + bool has_avx512_f() const; + + // Return true if the CPU supports AVX512DQ + bool has_avx512_dq() const; + + // Return true if the CPU supports AVX512_IFMA + bool has_avx512_ifma() const; + + // Return true if the CPU supports AVX512PF + bool has_avx512_pf() const; + + // Return true if the CPU supports AVX512ER + bool has_avx512_er() const; + + // Return true if the CPU supports AVX512CD + bool has_avx512_cd() const; + + // Return true if the CPU supports AVX512BW + bool has_avx512_bw() const; + + // Return true if the CPU supports AVX512VL + bool has_avx512_vl() const; + + // Return true if the CPU supports AVX512_VBMI + bool has_avx512_vbmi() const; + + // Return true if the CPU supports AVX512_VBMI2 + bool has_avx512_vbmi2() const; + + // Return true if the CPU supports AVX512_VNNI + bool has_avx512_vnni() const; + + // Return true if the CPU supports AVX512_BITALG + bool has_avx512_bitalg() const; + + // Return true if the CPU supports AVX512_VPOPCNTDQ + bool has_avx512_vpopcntdq() const; + + // Return true if the CPU supports AVX512_4VNNIW + bool has_avx512_4vnniw() const; + + // Return true if the CPU supports AVX512_4FMAPS + bool has_avx512_4fmaps() const; + + // Return true if the CPU supports AVX512_VP2INTERSECT + bool has_avx512_vp2intersect() const; + + // Return true if the CPU supports F16C + bool has_f16c() const; + + // Return true if the CPU supports NEON + bool has_neon() const; + + private: + // Private implementation + std::unique_ptr impl_; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h new file mode 100644 index 000000000..1d2ee69f7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../cpuinfo.h" + +namespace cpuid { + +struct cpuinfo::impl { + impl() + : m_has_fpu(false), + m_has_mmx(false), + m_has_sse(false), + m_has_sse2(false), + m_has_sse3(false), + m_has_ssse3(false), + m_has_sse4_1(false), + m_has_sse4_2(false), + m_has_pclmulqdq(false), + m_has_avx(false), + m_has_avx2(false), + m_has_avx512_f(false), + m_has_avx512_dq(false), + m_has_avx512_ifma(false), + m_has_avx512_pf(false), + m_has_avx512_er(false), + m_has_avx512_cd(false), + m_has_avx512_bw(false), + m_has_avx512_vl(false), + m_has_avx512_vbmi(false), + m_has_avx512_vbmi2(false), + m_has_avx512_vnni(false), + m_has_avx512_bitalg(false), + m_has_avx512_vpopcntdq(false), + m_has_avx512_4vnniw(false), + m_has_avx512_4fmaps(false), + m_has_avx512_vp2intersect(false), + m_has_f16c(false), + m_has_neon(false) {} + + bool m_has_fpu; + bool m_has_mmx; + bool m_has_sse; + bool m_has_sse2; + bool m_has_sse3; + bool m_has_ssse3; + bool m_has_sse4_1; + bool m_has_sse4_2; + bool m_has_pclmulqdq; + bool m_has_avx; + bool m_has_avx2; + bool m_has_avx512_f; + bool m_has_avx512_dq; + bool m_has_avx512_ifma; + bool m_has_avx512_pf; + bool m_has_avx512_er; + bool m_has_avx512_cd; + bool m_has_avx512_bw; + bool m_has_avx512_vl; + bool m_has_avx512_vbmi; + bool m_has_avx512_vbmi2; + bool m_has_avx512_vnni; + bool m_has_avx512_bitalg; + bool m_has_avx512_vpopcntdq; + bool m_has_avx512_4vnniw; + bool m_has_avx512_4fmaps; + bool m_has_avx512_vp2intersect; + bool m_has_f16c; + bool m_has_neon; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h new file mode 100644 index 000000000..5bea58665 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void extract_x86_flags(cpuinfo::impl& info, uint32_t ecx, uint32_t edx) { + info.m_has_fpu = (edx & (1 << 0)) != 0; + info.m_has_mmx = (edx & (1 << 23)) != 0; + info.m_has_sse = (edx & (1 << 25)) != 0; + info.m_has_sse2 = (edx & (1 << 26)) != 0; + info.m_has_sse3 = (ecx & (1 << 0)) != 0; + info.m_has_ssse3 = (ecx & (1 << 9)) != 0; + info.m_has_sse4_1 = (ecx & (1 << 19)) != 0; + info.m_has_sse4_2 = (ecx & (1 << 20)) != 0; + info.m_has_pclmulqdq = (ecx & (1 << 1)) != 0; + info.m_has_avx = (ecx & (1 << 28)) != 0; + info.m_has_f16c = (ecx & (1 << 29)) != 0; +} + +void extract_x86_extended_flags(cpuinfo::impl& info, uint32_t ebx, uint32_t ecx, + uint32_t edx) { + info.m_has_avx2 = (ebx & (1 << 5)) != 0; + info.m_has_avx512_f = (ebx & (1 << 16)) != 0; + info.m_has_avx512_dq = (ebx & (1 << 17)) != 0; + info.m_has_avx512_ifma = (ebx & (1 << 21)) != 0; + info.m_has_avx512_pf = (ebx & (1 << 26)) != 0; + info.m_has_avx512_er = (ebx & (1 << 27)) != 0; + info.m_has_avx512_cd = (ebx & (1 << 28)) != 0; + info.m_has_avx512_bw = (ebx & (1 << 30)) != 0; + info.m_has_avx512_vl = (ebx & (1 << 31)) != 0; + info.m_has_avx512_vbmi = (ecx & (1 << 1)) != 0; + info.m_has_avx512_vbmi2 = (ecx & (1 << 6)) != 0; + info.m_has_avx512_vnni = (ecx & (1 << 11)) != 0; + info.m_has_avx512_bitalg = (ecx & (1 << 12)) != 0; + info.m_has_avx512_vpopcntdq = (ecx & (1 << 14)) != 0; + info.m_has_avx512_4vnniw = (edx & (1 << 2)) != 0; + info.m_has_avx512_4fmaps = (edx & (1 << 3)) != 0; + info.m_has_avx512_vp2intersect = (edx & (1 << 8)) != 0; +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h new file mode 100644 index 000000000..697270b17 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" +#include "extract_x86_flags.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { + int registers[4]; + + // The register information per input can be extracted from here: + // http://en.wikipedia.org/wiki/CPUID + // + // CPUID should be called with EAX=0 first, as this will return the + // maximum supported EAX input value for future calls + __cpuid(registers, 0); + uint32_t maximum_eax = registers[0]; + + // Set registers for basic flag extraction, eax=1 + // All CPUs should support index=1 + if (maximum_eax >= 1U) { + __cpuid(registers, 1); + extract_x86_flags(info, registers[2], registers[3]); + } + + // Set registers for extended flags extraction, eax=7 and ecx=0 + // This operation is not supported on older CPUs, so it should be skipped + // to avoid incorrect results + if (maximum_eax >= 7U) { + __cpuidex(registers, 7, 0); + extract_x86_extended_flags(info, registers[1], registers[2], registers[3]); + } +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h new file mode 100644 index 000000000..3b52ef2b8 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h @@ -0,0 +1,9 @@ + +#pragma once + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { (void)info; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h new file mode 100644 index 000000000..a32bc157f --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type, + winrt::com_ptr& device) { + WINRT_ASSERT(!device); + + UINT flags = + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + + //#ifdef _DEBUG + // flags |= D3D11_CREATE_DEVICE_DEBUG; + //#endif + + return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, + D3D11_SDK_VERSION, device.put(), nullptr, nullptr); +} + +inline auto CreateD3DDevice() { + winrt::com_ptr device; + HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device); + + if (DXGI_ERROR_UNSUPPORTED == hr) { + CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device); + } + + return device; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc new file mode 100644 index 000000000..96a74ba99 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc @@ -0,0 +1,47 @@ +#include "direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + + namespace { + + typedef HRESULT(WINAPI* CreateDirect3D11DeviceFromDXGIDeviceFn)(IDXGIDevice*, + LPVOID*); + + struct D3DFuncs { + CreateDirect3D11DeviceFromDXGIDeviceFn CreateDirect3D11DeviceFromDXGIDevice = + nullptr; + + D3DFuncs() + { + auto handle = GetModuleHandle(L"d3d11.dll"); + if (!handle) { + return; + } + + CreateDirect3D11DeviceFromDXGIDevice = + reinterpret_cast( + GetProcAddress(handle, "CreateDirect3D11DeviceFromDXGIDevice")); + } + + static const D3DFuncs& instance() + { + static D3DFuncs funcs; + return funcs; + } + }; + + } // namespace + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice) + { + auto ptr = D3DFuncs::instance().CreateDirect3D11DeviceFromDXGIDevice; + if (ptr) { + return ptr(dxgiDevice, reinterpret_cast(graphicsDevice)); + } + + return E_NOTIMPL; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h new file mode 100644 index 000000000..4434815dc --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include "dxgi.h" + +namespace Windows { + namespace Graphics { + namespace DirectX { + namespace Direct3D11 { + struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0; + }; + + } // namespace Direct3D11 + } // namespace DirectX + } // namespace Graphics +} // namespace Windows + +namespace flutter_inappwebview_plugin +{ + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice); + + template + auto GetDXGIInterfaceFromObject( + winrt::Windows::Foundation::IInspectable const& object) + { + auto access = object.as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; + } + + template + auto TryGetDXGIInterfaceFromObject(const winrt::com_ptr& object) + { + auto access = object.try_as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + access->GetInterface(winrt::guid_of(), result.put_void()); + return result; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc new file mode 100644 index 000000000..6fa6ab0b2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc @@ -0,0 +1,244 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// 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 TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their 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 OWNER 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. + +#include "rohelper.h" + +#include +#include + +namespace rx { +template +bool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) { + outProc = reinterpret_cast(GetProcAddress(comBaseModule, name)); + return *outProc != nullptr; +} + +RoHelper::RoHelper(RO_INIT_TYPE init_type) + : mFpWindowsCreateStringReference(nullptr), + mFpGetActivationFactory(nullptr), + mFpWindowsCompareStringOrdinal(nullptr), + mFpCreateDispatcherQueueController(nullptr), + mFpWindowsDeleteString(nullptr), + mFpRoInitialize(nullptr), + mFpRoUninitialize(nullptr), + mWinRtAvailable(false), + mComBaseModule(nullptr), + mCoreMessagingModule(nullptr) { +#ifdef WINUWP + mFpWindowsCreateStringReference = &::WindowsCreateStringReference; + mFpRoInitialize = &::RoInitialize; + mFpRoUninitialize = &::RoUninitialize; + mFpWindowsDeleteString = &::WindowsDeleteString; + mFpGetActivationFactory = &::RoGetActivationFactory; + mFpWindowsCompareStringOrdinal = &::WindowsCompareStringOrdinal; + mFpCreateDispatcherQueueController = &::CreateDispatcherQueueController; + mWinRtAvailable = true; +#else + + mComBaseModule = LoadLibraryA("ComBase.dll"); + + if (mComBaseModule == nullptr) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCreateStringReference", + mFpWindowsCreateStringReference)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoGetActivationFactory", + mFpGetActivationFactory)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCompareStringOrdinal", + mFpWindowsCompareStringOrdinal)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsDeleteString", + mFpWindowsDeleteString)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoInitialize", mFpRoInitialize)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoUninitialize", mFpRoUninitialize)) { + return; + } + + mCoreMessagingModule = LoadLibraryA("coremessaging.dll"); + + if (mCoreMessagingModule == nullptr) { + return; + } + + if (!AssignProcAddress(mCoreMessagingModule, + "CreateDispatcherQueueController", + mFpCreateDispatcherQueueController)) { + return; + } + + auto result = RoInitialize(init_type); + + if (SUCCEEDED(result) || result == S_FALSE || result == RPC_E_CHANGED_MODE) { + mWinRtAvailable = true; + } +#endif +} + +RoHelper::~RoHelper() { +#ifndef WINUWP + if (mWinRtAvailable) { + RoUninitialize(); + } + + if (mCoreMessagingModule != nullptr) { + FreeLibrary(mCoreMessagingModule); + mCoreMessagingModule = nullptr; + } + + if (mComBaseModule != nullptr) { + FreeLibrary(mComBaseModule); + mComBaseModule = nullptr; + } +#endif +} + +bool RoHelper::WinRtAvailable() const { return mWinRtAvailable; } + +bool RoHelper::SupportedWindowsRelease() { + if (!mWinRtAvailable) { + return false; + } + + HSTRING className, contractName; + HSTRING_HEADER classNameHeader, contractNameHeader; + boolean isSupported = false; + + HRESULT hr = GetStringReference( + RuntimeClass_Windows_Foundation_Metadata_ApiInformation, &className, + &classNameHeader); + + if (FAILED(hr)) { + return !!isSupported; + } + + Microsoft::WRL::ComPtr< + ABI::Windows::Foundation::Metadata::IApiInformationStatics> + api; + + hr = GetActivationFactory( + className, + __uuidof(ABI::Windows::Foundation::Metadata::IApiInformationStatics), + &api); + + if (FAILED(hr)) { + return !!isSupported; + } + + hr = GetStringReference(L"Windows.Foundation.UniversalApiContract", + &contractName, &contractNameHeader); + if (FAILED(hr)) { + return !!isSupported; + } + + api->IsApiContractPresentByMajor(contractName, 6, &isSupported); + + return !!isSupported; +} + +HRESULT RoHelper::GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header) { + if (!mWinRtAvailable) { + return E_FAIL; + } + + const wchar_t* str = static_cast(source); + + unsigned int length; + HRESULT hr = SizeTToUInt32(::wcslen(str), &length); + if (FAILED(hr)) { + return hr; + } + + return mFpWindowsCreateStringReference(source, length, header, act); +} + +HRESULT RoHelper::GetActivationFactory(const HSTRING act, + const IID& interfaceId, void** fac) { + if (!mWinRtAvailable) { + return E_FAIL; + } + auto hr = mFpGetActivationFactory(act, interfaceId, fac); + return hr; +} + +HRESULT RoHelper::WindowsCompareStringOrdinal(HSTRING one, HSTRING two, + int* result) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsCompareStringOrdinal(one, two, result); +} + +HRESULT RoHelper::CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpCreateDispatcherQueueController(options, dispatcherQueueController); +} + +HRESULT RoHelper::WindowsDeleteString(HSTRING one) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsDeleteString(one); +} + +HRESULT RoHelper::RoInitialize(RO_INIT_TYPE type) { + return mFpRoInitialize(type); +} + +void RoHelper::RoUninitialize() { mFpRoUninitialize(); } +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h new file mode 100644 index 000000000..12e7e6cc5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h @@ -0,0 +1,97 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// 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 TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their 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 OWNER 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. + +#pragma once + +#include +#include +#include + +namespace rx { +class RoHelper { + public: + RoHelper(RO_INIT_TYPE init_type); + ~RoHelper(); + bool WinRtAvailable() const; + bool SupportedWindowsRelease(); + HRESULT GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header); + HRESULT GetActivationFactory(const HSTRING act, const IID& interfaceId, + void** fac); + HRESULT WindowsCompareStringOrdinal(HSTRING one, HSTRING two, int* result); + HRESULT CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController); + HRESULT WindowsDeleteString(HSTRING one); + HRESULT RoInitialize(RO_INIT_TYPE type); + void RoUninitialize(); + + private: + using WindowsCreateStringReference_ = HRESULT __stdcall(PCWSTR, UINT32, + HSTRING_HEADER*, + HSTRING*); + + using GetActivationFactory_ = HRESULT __stdcall(HSTRING, REFIID, void**); + + using WindowsCompareStringOrginal_ = HRESULT __stdcall(HSTRING, HSTRING, + int*); + + using WindowsDeleteString_ = HRESULT __stdcall(HSTRING); + + using CreateDispatcherQueueController_ = + HRESULT __stdcall(DispatcherQueueOptions, + ABI::Windows::System::IDispatcherQueueController**); + + using RoInitialize_ = HRESULT __stdcall(RO_INIT_TYPE); + using RoUninitialize_ = void __stdcall(); + + WindowsCreateStringReference_* mFpWindowsCreateStringReference; + GetActivationFactory_* mFpGetActivationFactory; + WindowsCompareStringOrginal_* mFpWindowsCompareStringOrdinal; + CreateDispatcherQueueController_* mFpCreateDispatcherQueueController; + WindowsDeleteString_* mFpWindowsDeleteString; + RoInitialize_* mFpRoInitialize; + RoUninitialize_* mFpRoUninitialize; + + bool mWinRtAvailable; + + HMODULE mComBaseModule; + HMODULE mCoreMessagingModule; +}; +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc new file mode 100644 index 000000000..7b2d8861d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc @@ -0,0 +1,54 @@ +#include "string_converter.h" + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string) { + if (utf16_string.empty()) { + return std::string(); + } + + auto src_length = static_cast(utf16_string.size()); + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), + src_length, nullptr, 0, nullptr, nullptr); + + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), src_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} + +std::wstring Utf16FromUtf8(std::string_view utf8_string) { + if (utf8_string.empty()) { + return std::wstring(); + } + + auto src_length = static_cast(utf8_string.size()); + int target_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, nullptr, 0); + + std::wstring utf16_string; + if (target_length <= 0 || target_length > utf16_string.max_size()) { + return utf16_string; + } + utf16_string.resize(target_length); + int converted_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, utf16_string.data(), target_length); + if (converted_length == 0) { + return std::wstring(); + } + return utf16_string; +} + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h new file mode 100644 index 000000000..0a71dbe0e --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string); +std::wstring Utf16FromUtf8(std::string_view utf8_string); +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h new file mode 100644 index 000000000..67f5f7c6b --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h @@ -0,0 +1,192 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * see skia/src/opts/SkSwizzler_opts.h + */ + +#pragma once + +#include "cpuid/cpuinfo.h" + +/** + * SK_CPU_SSE_LEVEL + * + * If defined, SK_CPU_SSE_LEVEL should be set to the highest supported level. + * On non-intel CPU this should be undefined. + */ +#define SK_CPU_SSE_LEVEL_SSE1 10 +#define SK_CPU_SSE_LEVEL_SSE2 20 +#define SK_CPU_SSE_LEVEL_SSE3 30 +#define SK_CPU_SSE_LEVEL_SSSE3 31 +#define SK_CPU_SSE_LEVEL_SSE41 41 +#define SK_CPU_SSE_LEVEL_SSE42 42 +#define SK_CPU_SSE_LEVEL_AVX 51 +#define SK_CPU_SSE_LEVEL_AVX2 52 +#define SK_CPU_SSE_LEVEL_SKX 60 + +// Are we in GCC/Clang? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(__SSE4_2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE42 +#elif defined(__SSE4_1__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE41 +#elif defined(__SSSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSSE3 +#elif defined(__SSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE3 +#elif defined(__SSE2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#endif +#endif + +// Are we in VisualStudio? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. 64-bit intel guarantees at least SSE2 support. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(_M_X64) || defined(_M_AMD64) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif defined(_M_IX86_FP) +#if _M_IX86_FP >= 2 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif _M_IX86_FP == 1 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE1 +#endif +#endif +#endif + +inline void RGBA_to_BGRA_portable(uint32_t* dst, const uint32_t* src, + int height, int src_stride, int dst_stride) { + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint8_t a = (src[x] >> 24) & 0xFF, b = (src[x] >> 16) & 0xFF, + g = (src[x] >> 8) & 0xFF, r = (src[x] >> 0) & 0xFF; + dst[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + +inline void RGBA_to_BGRA_SKX(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const uint8_t mask[64] = {2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, + 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, + 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, + 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, + 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15}; + const __m512i swapRB = _mm512_loadu_si512(mask); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 16) { + __m512i rgba = _mm512_loadu_si512((const __m512i*)rptr); + __m512i bgra = _mm512_shuffle_epi8(rgba, swapRB); + _mm512_storeu_si512((__m512i*)dptr, bgra); + + rptr += 16; + dptr += 16; + cw -= 16; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + +inline void RGBA_to_BGRA_AVX2(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const __m256i swapRB = + _mm256_setr_epi8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, + 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 8) { + __m256i rgba = _mm256_loadu_si256((const __m256i*)rptr); + __m256i bgra = _mm256_shuffle_epi8(rgba, swapRB); + _mm256_storeu_si256((__m256i*)dptr, bgra); + + rptr += 8; + dptr += 8; + cw -= 8; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + static cpuid::cpuinfo info; + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + if (info.has_avx512_f() && info.has_avx512_dq() && info.has_avx512_cd() && + info.has_avx512_bw() && info.has_avx512_vl()) { + return RGBA_to_BGRA_SKX(dst, src, height, src_stride, dst_stride); + } +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + if (info.has_avx2()) { + return RGBA_to_BGRA_AVX2(dst, src, height, src_stride, dst_stride); + } +#endif + + RGBA_to_BGRA_portable(dst, src, height, src_stride, dst_stride); +} diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp new file mode 100644 index 000000000..ac829ae9f --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp @@ -0,0 +1,74 @@ +#include "flutter_inappwebview_windows_plugin.h" + +#include + +#include "cookie_manager.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 "platform_util.h" +#include "webview_environment/webview_environment_manager.h" + + +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "rpcrt4.lib") // UuidCreate - Minimum supported OS Win 2000 +#pragma comment(lib, "WindowsApp.lib") + +namespace flutter_inappwebview_plugin +{ + // static + void FlutterInappwebviewWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows* registrar) + { + auto plugin = std::make_unique(registrar); + registrar->AddPlugin(std::move(plugin)); + } + + FlutterInappwebviewWindowsPlugin::FlutterInappwebviewWindowsPlugin(flutter::PluginRegistrarWindows* registrar) + : registrar(registrar) + { + webViewEnvironmentManager = std::make_unique(this); + inAppWebViewManager = std::make_unique(this); + inAppBrowserManager = std::make_unique(this); + headlessInAppWebViewManager = std::make_unique(this); + cookieManager = std::make_unique(this); + platformUtil = std::make_unique(this); + + window_proc_id = registrar->RegisterTopLevelWindowProcDelegate( + [this](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + return HandleWindowProc(hWnd, message, wParam, lParam); + }); + } + + FlutterInappwebviewWindowsPlugin::~FlutterInappwebviewWindowsPlugin() + { + if (registrar) { + registrar->UnregisterTopLevelWindowProcDelegate(window_proc_id); + } + webViewEnvironmentManager = nullptr; + inAppWebViewManager = nullptr; + inAppBrowserManager = nullptr; + headlessInAppWebViewManager = nullptr; + cookieManager = nullptr; + platformUtil = nullptr; + } + + + std::optional FlutterInappwebviewWindowsPlugin::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) + { + std::optional result = std::nullopt; + + if (platformUtil) { + result = platformUtil->HandleWindowProc(hWnd, message, wParam, lParam); + } + + return result; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h new file mode 100644 index 000000000..b195a5b34 --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h @@ -0,0 +1,44 @@ +#ifndef FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ +#define FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentManager; + class InAppWebViewManager; + class InAppBrowserManager; + class HeadlessInAppWebViewManager; + class CookieManager; + class PlatformUtil; + + class FlutterInappwebviewWindowsPlugin : public flutter::Plugin { + public: + flutter::PluginRegistrarWindows* registrar; + std::unique_ptr webViewEnvironmentManager; + std::unique_ptr inAppWebViewManager; + std::unique_ptr inAppBrowserManager; + std::unique_ptr headlessInAppWebViewManager; + std::unique_ptr cookieManager; + std::unique_ptr platformUtil; + + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); + + FlutterInappwebviewWindowsPlugin(flutter::PluginRegistrarWindows* registrar); + + virtual ~FlutterInappwebviewWindowsPlugin(); + + // Disallow copy and assign. + FlutterInappwebviewWindowsPlugin(const FlutterInappwebviewWindowsPlugin&) = delete; + FlutterInappwebviewWindowsPlugin& operator=(const FlutterInappwebviewWindowsPlugin&) = delete; + private: + // The ID of the WindowProc delegate registration. + int window_proc_id = -1; + std::optional FlutterInappwebviewWindowsPlugin::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + }; +} +#endif // FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp new file mode 100644 index 000000000..9fc6f6738 --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp @@ -0,0 +1,13 @@ +#include "include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h" + +#include + +#include "flutter_inappwebview_windows_plugin.h" + +void FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) +{ + flutter_inappwebview_plugin::FlutterInappwebviewWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp new file mode 100644 index 000000000..fc2ff2c13 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp @@ -0,0 +1,59 @@ +#include "../utils/log.h" +#include "headless_in_app_webview.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + HeadlessInAppWebView::HeadlessInAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const HeadlessInAppWebViewCreationParams& params, const HWND parentWindow, std::unique_ptr webView) + : plugin(plugin), id(params.id), + webView(std::move(webView)), + channelDelegate(std::make_unique(this, plugin->registrar->messenger())) + { + prepare(params); + ShowWindow(parentWindow, SW_HIDE); + } + + void HeadlessInAppWebView::prepare(const HeadlessInAppWebViewCreationParams& params) + { + if (!webView) { + return; + } + } + + void HeadlessInAppWebView::setSize(const std::shared_ptr size) const + { + if (!webView) { + return; + } + RECT rect = { + 0, 0, (LONG)size->width, (LONG)size->height + }; + webView->webViewController->put_Bounds(rect); + } + + std::shared_ptr HeadlessInAppWebView::getSize() const + { + if (!webView) { + return std::make_shared(-1.0, -1.0); + } + RECT rect; + webView->webViewController->get_Bounds(&rect); + auto width = rect.right - rect.left; + auto height = rect.bottom - rect.top; + return std::make_shared((double)width, (double)height); + } + + HeadlessInAppWebView::~HeadlessInAppWebView() + { + debugLog("dealloc HeadlessInAppWebView"); + HWND parentWindow = nullptr; + if (webView && webView->webViewController) { + webView->webViewController->get_ParentWindow(&parentWindow); + } + webView = nullptr; + if (parentWindow) { + DestroyWindow(parentWindow); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h new file mode 100644 index 000000000..0e4196028 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h @@ -0,0 +1,35 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../in_app_webview/in_app_webview.h" +#include "../types/size_2d.h" +#include "headless_webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + struct HeadlessInAppWebViewCreationParams { + const std::string id; + const std::shared_ptr initialSize; + }; + + class HeadlessInAppWebView + { + public: + static inline const wchar_t* CLASS_NAME = L"HeadlessInAppWebView"; + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::string id; + std::unique_ptr webView; + std::unique_ptr channelDelegate; + + HeadlessInAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const HeadlessInAppWebViewCreationParams& params, const HWND parentWindow, std::unique_ptr webView); + ~HeadlessInAppWebView(); + + void prepare(const HeadlessInAppWebViewCreationParams& params); + void setSize(const std::shared_ptr size) const; + std::shared_ptr getSize() const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp new file mode 100644 index 000000000..a74587d59 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/size_2d.h" +#include "../types/url_request.h" +#include "../types/user_script.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/map.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "../webview_environment/webview_environment_manager.h" +#include "headless_in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + HeadlessInAppWebViewManager::HeadlessInAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), HeadlessInAppWebViewManager::METHOD_CHANNEL_NAME) + { + windowClass_.lpszClassName = HeadlessInAppWebView::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + } + + void HeadlessInAppWebViewManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "run")) { + run(arguments, std::move(result)); + } + else { + result->NotImplemented(); + } + } + + void HeadlessInAppWebViewManager::run(const flutter::EncodableMap* arguments, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + if (!plugin) { + result_->Error("0", "Cannot create the HeadlessInAppWebView instance!"); + return; + } + + auto id = get_fl_map_value(*arguments, "id"); + auto params = get_fl_map_value(*arguments, "params"); + + auto initialSize = std::make_shared(get_fl_map_value(params, "initialSize")); + + auto settingsMap = get_fl_map_value(params, "initialSettings"); + auto urlRequestMap = get_optional_fl_map_value(params, "initialUrlRequest"); + auto initialFile = get_optional_fl_map_value(params, "initialFile"); + auto initialDataMap = get_optional_fl_map_value(params, "initialData"); + auto initialUserScriptList = get_optional_fl_map_value(params, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(params, "webViewEnvironmentId"); + + RECT bounds; + GetClientRect(plugin->registrar->GetView()->GetNativeWindow(), &bounds); + + auto initialWidth = initialSize->width >= 0 ? initialSize->width : bounds.right - bounds.left; + auto initialHeight = initialSize->height >= 0 ? initialSize->height : bounds.bottom - bounds.top; + + auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0, + 0, (int)initialWidth, (int)initialHeight, + plugin->registrar->GetView()->GetNativeWindow(), + nullptr, + windowClass_.hInstance, nullptr); + + auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + auto initialSettings = std::make_shared(settingsMap); + + InAppWebView::createInAppWebViewEnv(hwnd, false, webViewEnvironment, initialSettings, + [=](wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + { + if (plugin && webViewEnv && webViewController) { + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppWebViewCreationParams params = { + id, + std::move(initialSettings), + initialUserScripts + }; + + auto inAppWebView = std::make_unique(plugin, params, hwnd, + std::move(webViewEnv), std::move(webViewController), nullptr + ); + + HeadlessInAppWebViewCreationParams headlessParams = { + id, + std::move(initialSize) + }; + + auto headlessInAppWebView = std::make_unique(plugin, + headlessParams, + hwnd, + std::move(inAppWebView)); + + headlessInAppWebView->webView->initChannel(std::nullopt, std::nullopt); + + if (headlessInAppWebView->channelDelegate) { + headlessInAppWebView->channelDelegate->onWebViewCreated(); + } + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + if (urlRequest.has_value()) { + headlessInAppWebView->webView->loadUrl(urlRequest.value()); + } + else if (initialFile.has_value()) { + headlessInAppWebView->webView->loadFile(initialFile.value()); + } + else if (initialDataMap.has_value()) { + headlessInAppWebView->webView->loadData(get_fl_map_value(initialDataMap.value(), "data")); + } + + webViews.insert({ id, std::move(headlessInAppWebView) }); + + result_->Success(true); + } + else { + result_->Error("0", "Cannot create the HeadlessInAppWebView instance!"); + } + } + ); + } + + HeadlessInAppWebViewManager::~HeadlessInAppWebViewManager() + { + debugLog("dealloc HeadlessInAppWebViewManager"); + webViews.clear(); + UnregisterClass(windowClass_.lpszClassName, nullptr); + plugin = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h new file mode 100644 index 000000000..1e52c51f4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h @@ -0,0 +1,36 @@ +#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 + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "headless_in_app_webview.h" + +namespace flutter_inappwebview_plugin +{ + class HeadlessInAppWebViewManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViews; + + HeadlessInAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~HeadlessInAppWebViewManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void run(const flutter::EncodableMap* arguments, std::unique_ptr> result); + private: + WNDCLASS windowClass_ = {}; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp new file mode 100644 index 000000000..95099be68 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp @@ -0,0 +1,62 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "headless_in_app_webview.h" +#include "headless_webview_channel_delegate.h" + +#include "headless_in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + HeadlessWebViewChannelDelegate::HeadlessWebViewChannelDelegate(HeadlessInAppWebView* webView, flutter::BinaryMessenger* messenger) + : webView(webView), ChannelDelegate(messenger, HeadlessInAppWebView::METHOD_CHANNEL_NAME_PREFIX + variant_to_string(webView->id)) + {} + + void HeadlessWebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + if (!webView) { + result->Success(); + return; + } + + // auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "dispose")) { + if (webView->plugin && webView->plugin->headlessInAppWebViewManager) { + std::map>& webViews = webView->plugin->headlessInAppWebViewManager->webViews; + auto& id = webView->id; + if (map_contains(webViews, id)) { + if (webView->channelDelegate) { + webView->channelDelegate->UnregisterMethodCallHandler(); + if (webView->webView && webView->webView->channelDelegate) { + webView->webView->channelDelegate->UnregisterMethodCallHandler(); + } + } + webViews.erase(id); + } + } + result->Success(); + } + else { + result->NotImplemented(); + } + } + + void HeadlessWebViewChannelDelegate::onWebViewCreated() const + { + if (!channel) { + return; + } + + channel->InvokeMethod("onWebViewCreated", nullptr); + } + + HeadlessWebViewChannelDelegate::~HeadlessWebViewChannelDelegate() + { + debugLog("dealloc HeadlessWebViewChannelDelegate"); + webView = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h new file mode 100644 index 000000000..15d3a8edb --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ + +#include "../types/channel_delegate.h" +#include + +namespace flutter_inappwebview_plugin +{ + class HeadlessInAppWebView; + + class HeadlessWebViewChannelDelegate : public ChannelDelegate + { + public: + HeadlessInAppWebView* webView; + + HeadlessWebViewChannelDelegate(HeadlessInAppWebView* webView, flutter::BinaryMessenger* messenger); + ~HeadlessWebViewChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onWebViewCreated() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp new file mode 100644 index 000000000..d85f11102 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp @@ -0,0 +1,260 @@ +#include + +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../webview_environment/webview_environment_manager.h" +#include "in_app_browser.h" +#include "in_app_browser_manager.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowser::InAppBrowser(const FlutterInappwebviewWindowsPlugin* plugin, const InAppBrowserCreationParams& params) + : plugin(plugin), + m_hInstance(GetModuleHandle(nullptr)), + id(params.id), + settings(params.initialSettings), + channelDelegate(std::make_unique(id, plugin->registrar->messenger())) + { + + WNDCLASS wndClass = {}; + wndClass.lpszClassName = InAppBrowser::CLASS_NAME; + wndClass.hInstance = m_hInstance; + wndClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wndClass.lpfnWndProc = InAppBrowser::WndProc; + + RegisterClass(&wndClass); + + auto parentWindow = plugin->registrar->GetView()->GetNativeWindow(); + RECT bounds; + GetWindowRect(parentWindow, &bounds); + + auto x = CW_USEDEFAULT; + auto y = CW_USEDEFAULT; + auto width = bounds.right - bounds.left; + auto height = bounds.bottom - bounds.top; + + if (settings->windowFrame) { + x = (int)settings->windowFrame->x; + y = (int)settings->windowFrame->y; + width = (int)settings->windowFrame->width; + height = (int)settings->windowFrame->height; + } + + m_hWnd = CreateWindowEx( + WS_EX_LAYERED, // Optional window styles. + wndClass.lpszClassName, // Window class + + settings->toolbarTopFixedTitle.empty() ? L"" : utf8_to_wide(settings->toolbarTopFixedTitle).c_str(), // Window text + + settings->windowType == InAppBrowserWindowType::window ? WS_OVERLAPPEDWINDOW : (WS_CHILDWINDOW | WS_OVERLAPPEDWINDOW), // Window style + + // Position + x, y, + // Size + width, height, + + settings->windowType == InAppBrowserWindowType::window ? nullptr : parentWindow, // Parent window + nullptr, // Menu + wndClass.hInstance, // Instance handle + this // Additional application data + ); + + SetLayeredWindowAttributes(m_hWnd, 0, (BYTE)(255 * settings->windowAlphaValue), LWA_ALPHA); + + ShowWindow(m_hWnd, settings->hidden ? SW_HIDE : SW_SHOW); + + InAppWebViewCreationParams webViewParams = { + id, + params.initialWebViewSettings, + params.initialUserScripts + }; + + auto webViewEnvironment = params.webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, params.webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(params.webViewEnvironmentId.value()).get() : nullptr; + + InAppWebView::createInAppWebViewEnv(m_hWnd, false, webViewEnvironment, params.initialWebViewSettings, + [this, params, webViewParams](wil::com_ptr webViewEnv, wil::com_ptr webViewController, wil::com_ptr webViewCompositionController) -> void + { + if (webViewEnv && webViewController) { + webView = std::make_unique(this, this->plugin, webViewParams, m_hWnd, std::move(webViewEnv), std::move(webViewController), nullptr); + webView->initChannel(std::nullopt, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id); + + if (channelDelegate) { + channelDelegate->onBrowserCreated(); + } + + 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()); + } + } + else { + std::cerr << "Cannot create the InAppWebView instance!" << std::endl; + close(); + } + }); + } + + void InAppBrowser::close() const + { + DestroyWindow(m_hWnd); + } + + void InAppBrowser::show() const + { + ShowWindow(m_hWnd, SW_SHOW); + } + + void InAppBrowser::hide() const + { + ShowWindow(m_hWnd, SW_HIDE); + } + + bool InAppBrowser::isHidden() const + { + return !IsWindowVisible(m_hWnd); + } + + void InAppBrowser::setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap) + { + if (webView) { + webView->setSettings(std::make_shared(newSettingsMap), newSettingsMap); + } + + if (fl_map_contains_not_null(newSettingsMap, "hidden") && settings->hidden != newSettings->hidden) { + newSettings->hidden ? hide() : show(); + } + + if (fl_map_contains_not_null(newSettingsMap, "toolbarTopFixedTitle") && !string_equals(settings->toolbarTopFixedTitle, newSettings->toolbarTopFixedTitle) && !newSettings->toolbarTopFixedTitle.empty()) { + SetWindowText(m_hWnd, utf8_to_wide(newSettings->toolbarTopFixedTitle).c_str()); + } + + if (fl_map_contains_not_null(newSettingsMap, "windowAlphaValue") && settings->windowAlphaValue != newSettings->windowAlphaValue) { + SetLayeredWindowAttributes(m_hWnd, 0, (BYTE)(255 * newSettings->windowAlphaValue), LWA_ALPHA); + } + + if (fl_map_contains_not_null(newSettingsMap, "windowFrame")) { + auto x = (int)newSettings->windowFrame->x; + auto y = (int)newSettings->windowFrame->y; + auto width = (int)newSettings->windowFrame->width; + auto height = (int)newSettings->windowFrame->height; + MoveWindow(m_hWnd, x, y, width, height, true); + } + + settings = newSettings; + } + + flutter::EncodableValue InAppBrowser::getSettings() const + { + if (!settings || !webView) { + return make_fl_value(); + } + + auto encodableMap = settings->getRealSettings(this); + encodableMap.merge(std::get(webView->getSettings())); + return encodableMap; + } + + void InAppBrowser::didChangeTitle(const std::optional& title) const + { + if (title.has_value() && settings->toolbarTopFixedTitle.empty()) { + SetWindowText(m_hWnd, utf8_to_wide(title.value()).c_str()); + } + } + + LRESULT CALLBACK InAppBrowser::WndProc( + HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam + ) noexcept + { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); + } + else if (InAppBrowser* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); + } + + LRESULT InAppBrowser::MessageHandler( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam + ) noexcept + { + switch (message) { + case WM_DESTROY: { + // might receive multiple WM_DESTROY messages. + if (!destroyed_) { + destroyed_ = true; + + if (channelDelegate) { + channelDelegate->onExit(); + } + + if (channelDelegate) { + channelDelegate->UnregisterMethodCallHandler(); + if (webView && webView->channelDelegate) { + webView->channelDelegate->UnregisterMethodCallHandler(); + } + } + webView.reset(); + + if (plugin && plugin->inAppBrowserManager) { + plugin->inAppBrowserManager->browsers.erase(id); + } + } + return 0; + } + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + return 0; + } + case WM_SIZE: { + RECT bounds; + GetClientRect(hwnd, &bounds); + if (webView) { + webView->webViewController->put_Bounds(bounds); + } + return 0; + } + case WM_ACTIVATE: { + return 0; + } + } + + return DefWindowProc(hwnd, message, wparam, lparam); + } + + InAppBrowser* InAppBrowser::GetThisFromHandle(HWND const window) noexcept + { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); + } + + InAppBrowser::~InAppBrowser() + { + debugLog("dealloc InAppBrowser"); + webView.reset(); + SetWindowLongPtr(m_hWnd, GWLP_USERDATA, 0); + plugin = nullptr; + } + +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h new file mode 100644 index 000000000..3c1181c0a --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h @@ -0,0 +1,71 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ + +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#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 +{ + struct InAppBrowserCreationParams + { + const std::string id; + const std::optional> urlRequest; + const std::optional assetFilePath; + const std::optional data; + const std::shared_ptr initialSettings; + const std::shared_ptr initialWebViewSettings; + const std::optional>> initialUserScripts; + const std::optional webViewEnvironmentId; + }; + + class InAppBrowser { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_"; + static inline const wchar_t* CLASS_NAME = L"InAppBrowser"; + + static LRESULT CALLBACK WndProc(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam) noexcept; + + const FlutterInappwebviewWindowsPlugin* plugin; + const std::string id; + std::unique_ptr webView; + std::unique_ptr channelDelegate; + std::shared_ptr settings; + + InAppBrowser(const FlutterInappwebviewWindowsPlugin* plugin, const InAppBrowserCreationParams& params); + ~InAppBrowser(); + + void close() const; + void show() const; + void hide() const; + bool isHidden() const; + void setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap); + flutter::EncodableValue getSettings() const; + + void didChangeTitle(const std::optional& title) const; + HWND getHWND() const + { + return m_hWnd; + } + private: + const HINSTANCE m_hInstance; + HWND m_hWnd; + bool destroyed_ = false; + static InAppBrowser* GetThisFromHandle(HWND window) noexcept; + LRESULT MessageHandler(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam) noexcept; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp new file mode 100644 index 000000000..34aa966e0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp @@ -0,0 +1,37 @@ +#include "../utils/log.h" +#include "in_app_browser.h" +#include "in_app_browser_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowserChannelDelegate::InAppBrowserChannelDelegate(const std::string& id, flutter::BinaryMessenger* messenger) + : ChannelDelegate(messenger, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id) + {} + + void InAppBrowserChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + result->NotImplemented(); + } + + void InAppBrowserChannelDelegate::onBrowserCreated() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onBrowserCreated", nullptr); + } + + void InAppBrowserChannelDelegate::onExit() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onExit", nullptr); + } + + InAppBrowserChannelDelegate::~InAppBrowserChannelDelegate() + { + debugLog("dealloc InAppBrowserChannelDelegate"); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h new file mode 100644 index 000000000..d0032cffd --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h @@ -0,0 +1,26 @@ +#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 InAppBrowserChannelDelegate : public ChannelDelegate + { + public: + InAppBrowserChannelDelegate(const std::string& id, flutter::BinaryMessenger* messenger); + ~InAppBrowserChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onBrowserCreated() const; + void onExit() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp new file mode 100644 index 000000000..7f210f7bb --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp @@ -0,0 +1,94 @@ +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "../types/user_script.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "in_app_browser_manager.h" +#include "in_app_browser_settings.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowserManager::InAppBrowserManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), InAppBrowserManager::METHOD_CHANNEL_NAME) + {} + + void InAppBrowserManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "open")) { + if (plugin) { + createInAppBrowser(arguments); + result->Success(true); + } + else { + result->Error("0", "Cannot create the InAppBrowser instance!"); + + } + } + else if (string_equals(methodName, "openWithSystemBrowser")) { + auto url = get_fl_map_value(*arguments, "url"); + + int status = static_cast(reinterpret_cast( + ShellExecute(nullptr, TEXT("open"), utf8_to_wide(url).c_str(), + nullptr, nullptr, SW_SHOWNORMAL))); + + // Anything >32 indicates success. + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea#return-value + if (status <= 32) { + std::cerr << "Failed to open " << url << ": ShellExecute error code " << status << std::endl; + } + } + else { + result->NotImplemented(); + } + } + + void InAppBrowserManager::createInAppBrowser(const flutter::EncodableMap* arguments) + { + auto id = get_fl_map_value(*arguments, "id"); + auto urlRequestMap = get_optional_fl_map_value(*arguments, "urlRequest"); + auto assetFilePath = get_optional_fl_map_value(*arguments, "assetFilePath"); + auto data = get_optional_fl_map_value(*arguments, "data"); + auto initialUserScriptList = get_optional_fl_map_value(*arguments, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(*arguments, "webViewEnvironmentId"); + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + + auto settingsMap = get_fl_map_value(*arguments, "settings"); + auto initialSettings = std::make_unique(settingsMap); + auto initialWebViewSettings = std::make_unique(settingsMap); + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppBrowserCreationParams params = { + id, + urlRequest, + assetFilePath, + data, + std::move(initialSettings), + std::move(initialWebViewSettings), + initialUserScripts, + webViewEnvironmentId + }; + + auto inAppBrowser = std::make_unique(plugin, params); + browsers.insert({ id, std::move(inAppBrowser) }); + } + + InAppBrowserManager::~InAppBrowserManager() + { + debugLog("dealloc InAppBrowserManager"); + browsers.clear(); + plugin = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h new file mode 100644 index 000000000..c8b53a27f --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ + +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowserManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> browsers; + + InAppBrowserManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~InAppBrowserManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createInAppBrowser(const flutter::EncodableMap* arguments); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp new file mode 100644 index 000000000..dea7f8621 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp @@ -0,0 +1,76 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "in_app_browser_settings.h" + +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin +{ + namespace + { + InAppBrowserWindowType inAppBrowserWindowTypeFromString(const std::string& s) + { + if (string_equals(s, "CHILD")) { + return InAppBrowserWindowType::child; + } + return InAppBrowserWindowType::window; + } + + std::string inAppBrowserWindowTypeToString(const InAppBrowserWindowType& t) + { + switch (t) { + case InAppBrowserWindowType::child: + return "CHILD"; + default: + return "WINDOW"; + } + } + } + + InAppBrowserSettings::InAppBrowserSettings() {}; + + InAppBrowserSettings::InAppBrowserSettings(const flutter::EncodableMap& encodableMap) + { + hidden = get_fl_map_value(encodableMap, "hidden", hidden); + windowType = inAppBrowserWindowTypeFromString(get_fl_map_value(encodableMap, "windowType", inAppBrowserWindowTypeToString(InAppBrowserWindowType::window))); + toolbarTopFixedTitle = get_fl_map_value(encodableMap, "toolbarTopFixedTitle", toolbarTopFixedTitle); + windowAlphaValue = get_fl_map_value(encodableMap, "windowAlphaValue", windowAlphaValue); + auto windowFrameMap = get_optional_fl_map_value(encodableMap, "windowFrame"); + if (windowFrameMap.has_value()) { + windowFrame = std::make_shared(windowFrameMap.value()); + } + } + + flutter::EncodableMap InAppBrowserSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"hidden", hidden}, + {"windowType", inAppBrowserWindowTypeToString(windowType)}, + {"toolbarTopFixedTitle", toolbarTopFixedTitle}, + {"windowAlphaValue", windowAlphaValue}, + {"windowFrame", windowFrame ? windowFrame->toEncodableMap() : make_fl_value()}, + }; + } + + flutter::EncodableMap InAppBrowserSettings::getRealSettings(const InAppBrowser* inAppBrowser) const + { + auto settingsMap = toEncodableMap(); + settingsMap["hidden"] = inAppBrowser->isHidden(); + + BYTE alphaValue = 0; + GetLayeredWindowAttributes(inAppBrowser->getHWND(), nullptr, &alphaValue, nullptr); + settingsMap["windowAlphaValue"] = (double)alphaValue; + + RECT position; + GetWindowRect(inAppBrowser->getHWND(), &position); + settingsMap["windowFrame"] = std::make_unique(position.left, position.top, position.right - position.left, position.bottom - position.top)->toEncodableMap(); + + return settingsMap; + } + + InAppBrowserSettings::~InAppBrowserSettings() + { + debugLog("dealloc InAppBrowserSettings"); + }; +} diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h new file mode 100644 index 000000000..d5be8ee56 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ + +#include +#include +#include + +#include "../types/rect.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowser; + + enum class InAppBrowserWindowType { + window, + child + }; + + class InAppBrowserSettings + { + public: + bool hidden = false; + InAppBrowserWindowType windowType = InAppBrowserWindowType::window; + std::string toolbarTopFixedTitle; + double windowAlphaValue = 1.0; + std::shared_ptr windowFrame; + + InAppBrowserSettings(); + InAppBrowserSettings(const flutter::EncodableMap& encodableMap); + ~InAppBrowserSettings(); + + flutter::EncodableMap toEncodableMap() const; + flutter::EncodableMap getRealSettings(const InAppBrowser* inAppBrowser) const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp new file mode 100644 index 000000000..950d245c9 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp @@ -0,0 +1,2759 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../custom_platform_view/util/composition.desktop.interop.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../types/client_cert_response.h" +#include "../types/create_window_action.h" +#include "../types/http_auth_response.h" +#include "../types/javascript_handler_function_data.h" +#include "../types/server_trust_auth_response.h" +#include "../types/ssl_error.h" +#include "../types/url_credential.h" +#include "../types/web_resource_error.h" +#include "../types/web_resource_request.h" +#include "../utils/base64.h" +#include "../utils/log.h" +#include "../utils/map.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "../utils/uri.h" +#include "in_app_webview.h" +#include "in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + InAppWebView::InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : plugin(plugin), id(params.id), + webViewEnv(std::move(webViewEnv)), webViewController(std::move(webViewController)), webViewCompositionController(std::move(webViewCompositionController)), + settings(params.initialSettings), userContentController(std::make_unique(this)) + { + if (failedAndLog(this->webViewController->get_CoreWebView2(webView.put()))) { + std::cerr << "Cannot create CoreWebView2." << std::endl; + } + + if (this->webViewCompositionController) { + if (!createSurface(parentWindow, plugin->inAppWebViewManager->compositor())) { + std::cerr << "Cannot create InAppWebView surface." << std::endl; + } + registerSurfaceEventHandlers(); + } + else { + this->webViewController->put_IsVisible(true); + // Resize WebView to fit the bounds of the parent window + RECT bounds; + GetClientRect(parentWindow, &bounds); + this->webViewController->put_Bounds(bounds); + } + + prepare(params); + } + + InAppWebView::InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : InAppWebView(plugin, params, parentWindow, std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController)) + { + this->inAppBrowser = inAppBrowser; + } + + void InAppWebView::createInAppWebViewEnv(const HWND parentWindow, const bool& willBeSurface, WebViewEnvironment* webViewEnvironment, const std::shared_ptr initialSettings, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler) + { + auto callback = [parentWindow, willBeSurface, completionHandler, initialSettings](HRESULT result, wil::com_ptr env) -> HRESULT + { + if (failedAndLog(result) || !env) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + wil::com_ptr webViewEnv3; + wil::com_ptr webViewEnv10; + wil::com_ptr options; + if (initialSettings && succeededOrLog(env->QueryInterface(IID_PPV_ARGS(&webViewEnv10))) && succeededOrLog(webViewEnv10->CreateCoreWebView2ControllerOptions(&options))) { + options->put_IsInPrivateModeEnabled(initialSettings->incognito); + } + else { + webViewEnv10 = nullptr; + options = nullptr; + failedLog(env->QueryInterface(IID_PPV_ARGS(&webViewEnv3))); + } + if (willBeSurface && (webViewEnv10 || webViewEnv3)) { + if (webViewEnv10 && options) { + failedLog(webViewEnv10->CreateCoreWebView2CompositionControllerWithOptions(parentWindow, options.get(), Callback( + [completionHandler, env](HRESULT result, wil::com_ptr compositionController) -> HRESULT + { + wil::com_ptr webViewController = compositionController.try_query(); + + if (failedAndLog(result) || !webViewController) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + ICoreWebView2Controller3* webViewController3; + if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_BoundsMode(COREWEBVIEW2_BOUNDS_MODE_USE_RAW_PIXELS); + webViewController3->put_ShouldDetectMonitorScaleChanges(FALSE); + webViewController3->put_RasterizationScale(1.0); + } + + completionHandler(std::move(env), std::move(webViewController), std::move(compositionController)); + return S_OK; + } + ).Get())); + } + else { + failedLog(webViewEnv3->CreateCoreWebView2CompositionController(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr compositionController) -> HRESULT + { + wil::com_ptr webViewController = compositionController.try_query(); + + if (failedAndLog(result) || !webViewController) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + ICoreWebView2Controller3* webViewController3; + if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_BoundsMode(COREWEBVIEW2_BOUNDS_MODE_USE_RAW_PIXELS); + webViewController3->put_ShouldDetectMonitorScaleChanges(FALSE); + webViewController3->put_RasterizationScale(1.0); + } + + completionHandler(std::move(env), std::move(webViewController), std::move(compositionController)); + return S_OK; + } + ).Get())); + } + } + else { + if (webViewEnv10 && options) { + failedLog(webViewEnv10->CreateCoreWebView2ControllerWithOptions(parentWindow, options.get(), Callback( + [completionHandler, env](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (failedAndLog(result) || !controller) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + completionHandler(std::move(env), std::move(controller), nullptr); + return S_OK; + }).Get())); + } + else { + failedLog(env->CreateCoreWebView2Controller(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (failedAndLog(result) || !controller) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + completionHandler(std::move(env), std::move(controller), nullptr); + return S_OK; + }).Get())); + } + } + return S_OK; + }; + + HRESULT hr; + if (webViewEnvironment && webViewEnvironment->getEnvironment()) { + hr = callback(S_OK, webViewEnvironment->getEnvironment()); + } + else { + hr = CreateCoreWebView2EnvironmentWithOptions( + nullptr, nullptr, nullptr, + Callback(callback).Get()); + } + + if (failedAndLog(hr)) { + completionHandler(nullptr, nullptr, nullptr); + } + } + + void InAppWebView::initChannel(const std::optional> viewId, const std::optional channelName) + { + if (viewId.has_value()) { + id = viewId.value(); + } + channelDelegate = channelName.has_value() ? std::make_unique(this, plugin->registrar->messenger(), channelName.value()) : + std::make_unique(this, plugin->registrar->messenger()); + } + + void InAppWebView::prepare(const InAppWebViewCreationParams& params) + { + if (!webView) { + return; + } + + javaScriptBridgeEnabled = settings->javaScriptBridgeEnabled; + + wil::com_ptr webView2Settings; + auto hrWebView2Settings = webView->get_Settings(&webView2Settings); + if (succeededOrLog(hrWebView2Settings)) { + webView2Settings->put_IsScriptEnabled(settings->javaScriptEnabled); + webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); + webView2Settings->put_AreDevToolsEnabled(settings->isInspectable); + webView2Settings->put_AreDefaultContextMenusEnabled(!settings->disableContextMenu); + webView2Settings->put_IsBuiltInErrorPageEnabled(!settings->disableDefaultErrorPage); + webView2Settings->put_IsStatusBarEnabled(settings->statusBarEnabled); + + if (auto webView2Settings2 = webView2Settings.try_query()) { + if (!settings->userAgent.empty()) { + webView2Settings2->put_UserAgent(utf8_to_wide(settings->userAgent).c_str()); + } + } + + if (auto webView2Settings3 = webView2Settings.try_query()) { + webView2Settings3->put_AreBrowserAcceleratorKeysEnabled(settings->browserAcceleratorKeysEnabled); + } + + if (auto webView2Settings4 = webView2Settings.try_query()) { + webView2Settings4->put_IsGeneralAutofillEnabled(settings->generalAutofillEnabled); + webView2Settings4->put_IsPasswordAutosaveEnabled(settings->passwordAutosaveEnabled); + } + + if (auto webView2Settings5 = webView2Settings.try_query()) { + webView2Settings5->put_IsPinchZoomEnabled(settings->pinchZoomEnabled); + } + + if (auto webView2Settings6 = webView2Settings.try_query()) { + webView2Settings6->put_IsSwipeNavigationEnabled(settings->allowsBackForwardNavigationGestures); + } + + if (auto webView2Settings7 = webView2Settings.try_query()) { + webView2Settings7->put_HiddenPdfToolbarItems((COREWEBVIEW2_PDF_TOOLBAR_ITEMS)settings->hiddenPdfToolbarItems); + } + + if (auto webView2Settings8 = webView2Settings.try_query()) { + webView2Settings8->put_IsReputationCheckingRequired(settings->reputationCheckingRequired); + } + + if (auto webView2Settings9 = webView2Settings.try_query()) { + webView2Settings9->put_IsNonClientRegionSupportEnabled(settings->nonClientRegionSupportEnabled); + } + } + + if (auto webViewController2 = webViewController.try_query()) { + if (settings->transparentBackground) { + webViewController2->put_DefaultBackgroundColor({ 0, 255, 255, 255 }); + } + } + + // required to make Runtime events work + failedLog(webView->CallDevToolsProtocolMethod(L"Runtime.enable", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + // required to make Page events work and to add User Scripts + failedLog(webView->CallDevToolsProtocolMethod(L"Page.enable", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + // required to use Network domain + failedLog(webView->CallDevToolsProtocolMethod(L"Network.enable", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + // required to use Fetch domain and implement the shouldOverrideUrlLoading event correctly + failedLog(webView->CallDevToolsProtocolMethod(L"Fetch.enable", L"{\"patterns\": [{\"resourceType\": \"Document\", \"requestStage\": \"Request\"}]}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + failedLog(webView->CallDevToolsProtocolMethod(L"Page.getFrameTree", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + auto treeJson = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + pageFrameId_ = treeJson["frameTree"]["frame"]["id"].get(); + } + return S_OK; + } + ).Get())); + + if (userContentController) { + if (javaScriptBridgeEnabled) { + auto pluginScriptsOriginAllowList = settings->pluginScriptsOriginAllowList; + auto pluginScriptsForMainFrameOnly = settings->pluginScriptsForMainFrameOnly; + + auto javaScriptBridgeOriginAllowList = settings->javaScriptBridgeOriginAllowList.has_value() ? settings->javaScriptBridgeOriginAllowList : pluginScriptsOriginAllowList; + auto javaScriptBridgeForMainFrameOnly = settings->javaScriptBridgeForMainFrameOnly.has_value() ? settings->javaScriptBridgeForMainFrameOnly.value() : pluginScriptsForMainFrameOnly; + userContentController->addPluginScript(std::move(JavaScriptBridgeJS::JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret, javaScriptBridgeOriginAllowList, javaScriptBridgeForMainFrameOnly))); + } + + if (params.initialUserScripts.has_value()) { + userContentController->addUserOnlyScripts(params.initialUserScripts.value()); + } + } + + registerEventHandlers(); + } + + void InAppWebView::registerEventHandlers() + { + if (!webView || !webViewController) { + return; + } + + auto add_AcceleratorKeyPressed_HResult = webViewController->add_AcceleratorKeyPressed( + Callback( + [this](ICoreWebView2Controller* sender, ICoreWebView2AcceleratorKeyPressedEventArgs* args) + { + if (channelDelegate) { + auto handled = settings->handleAcceleratorKeyPressed; + args->put_Handled(handled); + if (handled) { + auto detail = AcceleratorKeyPressedDetail::fromICoreWebView2AcceleratorKeyPressedEventArgs(args); + channelDelegate->onAcceleratorKeyPressed(std::move(detail)); + } + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_AcceleratorKeyPressed_HResult); + + auto add_ZoomFactorChanged_HResult = webViewController->add_ZoomFactorChanged( + Callback( + [this](ICoreWebView2Controller* sender, IUnknown* args) + { + double newScale; + if (succeededOrLog(sender->get_ZoomFactor(&newScale))) { + if (channelDelegate) { + channelDelegate->onZoomScaleChanged(zoomScaleFactor_, newScale); + } + zoomScaleFactor_ = newScale; + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ZoomFactorChanged_HResult); + + wil::com_ptr fetchRequestPausedEventReceiver; + if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(L"Fetch.requestPaused", &fetchRequestPausedEventReceiver))) { + auto add_DevToolsProtocolEventReceived_HResult = fetchRequestPausedEventReceiver->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + auto requestPausedData = nlohmann::json::parse(wide_to_utf8(json.get())); + + auto requestId = requestPausedData.at("requestId").get(); + auto resourceType = requestPausedData.at("resourceType").get(); + auto isResponseStage = requestPausedData.contains("responseStatusCode"); + auto frameId = requestPausedData.at("frameId").get(); + + auto request = requestPausedData.at("request").get(); + std::optional url = request.at("url").is_string() ? request.at("url").get() : std::optional{}; + std::optional urlFragment = request.contains("urlFragment") && request.at("urlFragment").is_string() ? request.at("urlFragment").get() : std::optional{}; + if (url.has_value() && urlFragment.has_value()) { + url = url.value() + urlFragment.value(); + } + auto isForMainFrame = pageFrameId_.empty() || string_equals(pageFrameId_, frameId); + + auto allowRequest = [this, requestId, url, isForMainFrame]() + { + failedAndLog(webView->CallDevToolsProtocolMethod(L"Fetch.continueRequest", + utf8_to_wide("{\"requestId\":\"" + requestId + "\"}").c_str(), + Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + if (channelDelegate && isForMainFrame) { + // if shouldOverrideUrlLoading is used, then call onLoadStart and onProgressChanged here + // to match the behaviour of the other platforms + channelDelegate->onLoadStart(url); + channelDelegate->onProgressChanged(0); + } + }; + + auto cancelRequest = [this, requestId]() + { + failedAndLog(webView->CallDevToolsProtocolMethod(L"Fetch.failRequest", + utf8_to_wide("{\"requestId\":\"" + requestId + "\", \"errorReason\": \"Aborted\"}").c_str(), + Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + }; + + if (!isResponseStage && channelDelegate && settings->useShouldOverrideUrlLoading && string_equals(resourceType, "Document")) { + std::optional method = request.at("method").is_string() ? request.at("method").get() : std::optional{}; + std::optional> headers = request.at("headers").is_object() ? request.at("headers").get>() : std::optional>{}; + std::optional redirectedRequestId = request.contains("redirectedRequestId") && request.at("redirectedRequestId").is_string() ? request.at("redirectedRequestId").get() : std::optional{}; + + std::optional> body = std::optional>{}; + auto hasPostData = request.contains("hasPostData") && request.at("hasPostData").is_boolean() && request.at("hasPostData").get() + && request.contains("postDataEntries") && request.at("postDataEntries").is_array(); + if (hasPostData) { + auto postDataEntries = request.at("postDataEntries").get>(); + + if (postDataEntries.size() > 0) { + body = std::vector{}; + for (auto const& entry : postDataEntries) { + if (entry.contains("bytes")) { + try { + auto entryData = base64_decode(entry.at("bytes").get()); + std::vector bytes(entryData.begin(), entryData.end()); + body->insert(body->end(), bytes.begin(), bytes.end()); + } + catch (const std::exception& err) { + debugLog("Error decoding base64 data"); + debugLog(err.what()); + body = std::optional>{}; + break; + } + } + } + } + } + + BOOL isRedirect = redirectedRequestId.has_value() && !redirectedRequestId.value().empty(); + + std::optional navigationType = isRedirect ? NavigationActionType::other : std::optional{}; + + auto urlRequest = std::make_shared(url, method, headers, body); + auto navigationAction = std::make_shared( + urlRequest, + isForMainFrame, + isRedirect, + navigationType + ); + + auto callback = std::make_unique(); + callback->nonNullSuccess = [this, allowRequest, cancelRequest](const NavigationActionPolicy actionPolicy) + { + if (actionPolicy == NavigationActionPolicy::allow) { + allowRequest(); + } + else { + cancelRequest(); + } + return false; + }; + auto defaultBehaviour = [this, allowRequest](const std::optional actionPolicy) + { + allowRequest(); + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->shouldOverrideUrlLoading(std::move(navigationAction), std::move(callback)); + } + else { + // check if a custom event listener is found and give back the opportunity to it to handle the request + // through the Chrome Dev Protocol API + if (!map_contains(devToolsProtocolEventListener_, std::string("Fetch.requestPaused"))) { + // if a custom event listener is not found, continue the request + allowRequest(); + } + } + } + + return S_OK; + }) + .Get(), nullptr); + failedAndLog(add_DevToolsProtocolEventReceived_HResult); + } + + auto add_NavigationStarting_HResult = webView->add_NavigationStarting( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) + { + isLoading_ = true; + + if (!channelDelegate) { + args->put_Cancel(false); + return S_OK; + } + + wil::unique_cotaskmem_string uri = nullptr; + std::optional url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + + wil::unique_cotaskmem_string requestMethod = nullptr; + wil::com_ptr requestHeaders = nullptr; + std::optional> headers = std::optional>{}; + if (SUCCEEDED(args->get_RequestHeaders(&requestHeaders))) { + headers = std::make_optional>({}); + wil::com_ptr iterator; + requestHeaders->GetIterator(&iterator); + BOOL hasCurrent = FALSE; + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { + wil::unique_cotaskmem_string name; + wil::unique_cotaskmem_string value; + + if (SUCCEEDED(iterator->GetCurrentHeader(&name, &value))) { + headers->insert({ wide_to_utf8(name.get()), wide_to_utf8(value.get()) }); + } + + BOOL hasNext = FALSE; + iterator->MoveNext(&hasNext); + } + + requestHeaders->GetHeader(L"Flutter-InAppWebView-Request-Method", &requestMethod); + requestHeaders->RemoveHeader(L"Flutter-InAppWebView-Request-Method"); + } + + std::optional method = requestMethod ? wide_to_utf8(requestMethod.get()) : std::optional{}; + + BOOL isUserInitiated; + if (FAILED(args->get_IsUserInitiated(&isUserInitiated))) { + isUserInitiated = FALSE; + } + + BOOL isRedirect; + if (FAILED(args->get_IsRedirected(&isRedirect))) { + isRedirect = FALSE; + } + + std::optional navigationType = std::nullopt; + wil::com_ptr args3; + if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&args3)))) { + COREWEBVIEW2_NAVIGATION_KIND navigationKind; + if (SUCCEEDED(args3->get_NavigationKind(&navigationKind))) { + switch (navigationKind) { + case COREWEBVIEW2_NAVIGATION_KIND_RELOAD: + navigationType = NavigationActionType::reload; + break; + case COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD: + navigationType = NavigationActionType::backForward; + break; + case COREWEBVIEW2_NAVIGATION_KIND_NEW_DOCUMENT: + if (isUserInitiated && !isRedirect) { + navigationType = NavigationActionType::linkActivated; + } + else { + navigationType = NavigationActionType::other; + } + break; + default: + navigationType = NavigationActionType::other; + } + } + } + + auto urlRequest = std::make_shared(url, method, headers, std::nullopt); + auto navigationAction = std::make_shared( + urlRequest, + true, + isRedirect, + navigationType + ); + + lastNavigationAction_ = navigationAction; + + UINT64 navigationId; + if (SUCCEEDED(args->get_NavigationId(&navigationId))) { + navigationActions_.insert({ navigationId, navigationAction }); + } + + // if shouldOverrideUrlLoading is not used, then call onLoadStart and onProgressChanged here + if (!settings->useShouldOverrideUrlLoading) { + channelDelegate->onLoadStart(url); + channelDelegate->onProgressChanged(0); + } + args->put_Cancel(false); + + return S_OK; + } + ).Get(), nullptr); + failedLog(add_NavigationStarting_HResult); + + auto add_ContentLoading_HResult = webView->add_ContentLoading( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) + { + if (channelDelegate) { + channelDelegate->onProgressChanged(33); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ContentLoading_HResult); + + auto add_NavigationCompleted_HResult = webView->add_NavigationCompleted( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) + { + isLoading_ = false; + previousAuthRequestFailureCount = 0; + + evaluateJavascript(JavaScriptBridgeJS::PLATFORM_READY_JS_SOURCE(), ContentWorld::page(), nullptr); + + std::shared_ptr navigationAction; + UINT64 navigationId; + if (SUCCEEDED(args->get_NavigationId(&navigationId))) { + navigationAction = map_at_or_null(navigationActions_, navigationId); + if (navigationAction) { + navigationActions_.erase(navigationId); + } + } + + COREWEBVIEW2_WEB_ERROR_STATUS webErrorType = COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN; + args->get_WebErrorStatus(&webErrorType); + + BOOL isSuccess; + args->get_IsSuccess(&isSuccess); + + if (channelDelegate) { + wil::unique_cotaskmem_string uri; + std::optional url = SUCCEEDED(webView->get_Source(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + + channelDelegate->onProgressChanged(100); + if (isSuccess) { + channelDelegate->onLoadStop(url); + } + else if (!InAppWebView::isSslError(webErrorType) && navigationAction) { + auto webResourceRequest = std::make_unique(url, navigationAction->request->method, navigationAction->request->headers, navigationAction->isForMainFrame); + int httpStatusCode = 0; + wil::com_ptr args2; + if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&args2))) && SUCCEEDED(args2->get_HttpStatusCode(&httpStatusCode)) && httpStatusCode >= 400) { + auto webResourceResponse = std::make_unique(std::optional{}, + std::optional{}, + httpStatusCode, + std::optional{}, + std::optional>{}, + std::optional>{}); + channelDelegate->onReceivedHttpError(std::move(webResourceRequest), std::move(webResourceResponse)); + } + else if (httpStatusCode < 400) { + auto webResourceError = std::make_unique(WebErrorStatusDescription[webErrorType], webErrorType); + channelDelegate->onReceivedError(std::move(webResourceRequest), std::move(webResourceError)); + } + } + } + + return S_OK; + } + ).Get(), nullptr); + failedLog(add_NavigationCompleted_HResult); + + auto add_DocumentTitleChanged_HResult = webView->add_DocumentTitleChanged(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + wil::unique_cotaskmem_string title; + sender->get_DocumentTitle(&title); + channelDelegate->onTitleChanged(title.is_valid() ? wide_to_utf8(title.get()) : std::optional{}); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_DocumentTitleChanged_HResult); + + auto add_HistoryChanged_HResult = webView->add_HistoryChanged(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + std::optional isReload = std::nullopt; + if (lastNavigationAction_ && lastNavigationAction_->navigationType.has_value()) { + isReload = lastNavigationAction_->navigationType.value() == NavigationActionType::reload; + } + channelDelegate->onUpdateVisitedHistory(getUrl(), isReload); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_HistoryChanged_HResult); + + auto add_WebMessageReceived_HResult = webView->add_WebMessageReceived(Callback( + [this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) + { + return this->onCallJsHandler(true, args); + } + ).Get(), nullptr); + failedLog(add_WebMessageReceived_HResult); + + wil::com_ptr consoleMessageReceiver; + if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(L"Runtime.consoleAPICalled", &consoleMessageReceiver))) { + auto consoleMessageReceiver_add_DevToolsProtocolEventReceived_HResult = consoleMessageReceiver->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + auto consoleMessageJson = nlohmann::json::parse(wide_to_utf8(json.get())); + + auto level = consoleMessageJson.at("type").get(); + int64_t messageLevel = 1; + if (string_equals(level, "log")) { + messageLevel = 1; + } + else if (string_equals(level, "debug")) { + messageLevel = 0; + } + else if (string_equals(level, "error")) { + messageLevel = 3; + } + else if (string_equals(level, "info")) { + messageLevel = 1; + } + else if (string_equals(level, "warn")) { + messageLevel = 2; + } + + auto consoleArgs = consoleMessageJson.at("args").get>(); + auto message = join(functional_map(consoleArgs, [](const nlohmann::json& json) { return json.contains("value") ? json.at("value").dump() : (json.contains("description") ? json.at("description").dump() : json.dump()); }), std::string{ " " }); + channelDelegate->onConsoleMessage(message, messageLevel); + } + + return S_OK; + }) + .Get(), nullptr); + failedLog(consoleMessageReceiver_add_DevToolsProtocolEventReceived_HResult); + } + + auto add_NewWindowRequested_HResult = webView->add_NewWindowRequested( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args) + { + wil::com_ptr deferral; + if (channelDelegate && plugin && plugin->inAppWebViewManager && succeededOrLog(args->GetDeferral(&deferral))) { + plugin->inAppWebViewManager->windowAutoincrementId++; + int64_t windowId = plugin->inAppWebViewManager->windowAutoincrementId; + auto newWindowRequestedArgs = std::make_unique(args, deferral); + plugin->inAppWebViewManager->windowWebViews.insert({ windowId, std::move(newWindowRequestedArgs) }); + + wil::unique_cotaskmem_string uri = nullptr; + std::optional url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + + BOOL hasGesture; + if (FAILED(args->get_IsUserInitiated(&hasGesture))) { + hasGesture = FALSE; + } + + wil::com_ptr webviewWindowFeatures; + std::optional> windowFeatures; + if (SUCCEEDED(args->get_WindowFeatures(&webviewWindowFeatures))) { + windowFeatures = std::make_unique(webviewWindowFeatures); + } + + auto urlRequest = std::make_shared(url, "GET", std::nullopt, std::nullopt); + auto createWindowAction = std::make_shared( + urlRequest, + windowId, + true, + hasGesture, + std::move(windowFeatures)); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, windowId, urlRequest, deferral, args](const std::optional handledByClient) + { + if (plugin && plugin->inAppWebViewManager && map_contains(plugin->inAppWebViewManager->windowWebViews, windowId)) { + plugin->inAppWebViewManager->windowWebViews.erase(windowId); + } + loadUrl(urlRequest); + failedLog(args->put_Handled(TRUE)); + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const bool handledByClient) + { + return !handledByClient; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onCreateWindow(std::move(createWindowAction), std::move(callback)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_NewWindowRequested_HResult); + + auto add_WindowCloseRequested_HResult = webView->add_WindowCloseRequested(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + channelDelegate->onCloseWindow(); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_WindowCloseRequested_HResult); + + auto add_PermissionRequested_HResult = webView->add_PermissionRequested(Callback( + [this](ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args) + { + wil::com_ptr deferral; + if (channelDelegate && succeededOrLog(args->GetDeferral(&deferral))) { + wil::unique_cotaskmem_string uri; + std::string url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : ""; + + COREWEBVIEW2_PERMISSION_KIND resource = COREWEBVIEW2_PERMISSION_KIND_UNKNOWN_PERMISSION; + failedLog(args->get_PermissionKind(&resource)); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> permissionResponse) + { + failedLog(args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY)); + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr permissionResponse) + { + auto action = permissionResponse->action; + if (action.has_value()) { + switch (action.value()) { + case PermissionResponseActionType::grant: + failedLog(args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW)); + break; + case PermissionResponseActionType::prompt: + failedLog(args->put_State(COREWEBVIEW2_PERMISSION_STATE_DEFAULT)); + break; + default: + failedLog(args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY)); + break; + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onPermissionRequest(url, { resource }, std::move(callback)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_PermissionRequested_HResult); + + failedLog(webView->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)); + auto add_WebResourceRequested_HResult = webView->add_WebResourceRequested( + Callback( + [this]( + ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args) + { + wil::com_ptr deferral; + wil::com_ptr webResourceRequest; + if (channelDelegate && succeededOrLog(args->get_Request(&webResourceRequest)) && succeededOrLog(args->GetDeferral(&deferral))) { + auto request = std::make_shared(webResourceRequest); + + // The add_WebResourceRequested event is by default raised for file, http, and https URI schemes. + // This is also raised for registered custom URI schemes. + // https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2792.45#add_webresourcerequested + auto url = request->url.has_value() ? request->url.value() : ""; + auto isCustomScheme = !url.empty() && !starts_with(url, std::string{ "file://" }) && !starts_with(url, std::string{ "http://" }) && !starts_with(url, std::string{ "https://" }); + + auto onLoadResourceWithCustomSchemeCallback = [this, deferral, request, args]() + { + if (channelDelegate) { + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + args->put_Response(response->toWebView2Response(webViewEnv)); + failedLog(deferral->Complete()); + return false; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onLoadResourceWithCustomScheme(request, std::move(callback)); + } + else { + failedLog(deferral->Complete()); + } + }; + + if (settings->useShouldInterceptRequest) { + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + args->put_Response(response->toWebView2Response(webViewEnv)); + failedLog(deferral->Complete()); + return false; + }; + callback->nullSuccess = [this, deferral, args, isCustomScheme, onLoadResourceWithCustomSchemeCallback]() + { + if (isCustomScheme) { + onLoadResourceWithCustomSchemeCallback(); + } + else { + failedLog(deferral->Complete()); + } + return false; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->shouldInterceptRequest(request, std::move(callback)); + } + else if (isCustomScheme) { + onLoadResourceWithCustomSchemeCallback(); + } + else { + failedLog(deferral->Complete()); + } + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_WebResourceRequested_HResult); + + auto add_ProcessFailed_HResult = webView->add_ProcessFailed( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* argsRaw) + { + if (!channelDelegate) { + return S_OK; + } + + wil::com_ptr args = argsRaw; + auto args2 = args.try_query(); + auto args3 = args.try_query(); + + COREWEBVIEW2_PROCESS_FAILED_REASON reason = COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED; + if (args2) { + args2->get_Reason(&reason); + } + + COREWEBVIEW2_PROCESS_FAILED_KIND kind; + if (succeededOrLog(args->get_ProcessFailedKind(&kind))) { + if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED) { + auto didCrash = reason == COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED; + auto detail = std::make_unique( + didCrash + ); + channelDelegate->onRenderProcessGone(std::move(detail)); + } + else if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE) { + channelDelegate->onRenderProcessUnresponsive(getUrl()); + } + else if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED) { + channelDelegate->onWebContentProcessDidTerminate(); + } + + auto frameInfos = std::optional>>{}; + wil::com_ptr frameInfoCollection; + wil::com_ptr frameIterator; + if (args2 && succeededOrLog(args2->get_FrameInfosForFailedProcess(&frameInfoCollection)) && frameInfoCollection && succeededOrLog(frameInfoCollection->GetIterator(&frameIterator))) { + frameInfos = std::vector>{}; + BOOL hasCurrent = FALSE; + while (SUCCEEDED(frameIterator->MoveNext(&hasCurrent)) && hasCurrent) { + wil::com_ptr frameInfo; + if (SUCCEEDED(frameIterator->GetCurrent(&frameInfo))) { + frameInfos.value().push_back(std::move(FrameInfo::fromICoreWebView2FrameInfo(frameInfo))); + } + BOOL hasNext = FALSE; + failedLog(frameIterator->MoveNext(&hasNext)); + } + } + + wil::unique_cotaskmem_string processDescription; + int exitCode; + wil::unique_cotaskmem_string failedModule; + + auto detail = std::make_unique( + (int64_t)kind, + args2 && succeededOrLog(args2->get_ExitCode(&exitCode)) ? exitCode : std::optional{}, + args2 && succeededOrLog(args2->get_ProcessDescription(&processDescription)) ? wide_to_utf8(processDescription.get()) : std::optional{}, + args2 ? (int64_t)reason : std::optional{}, + args3 && succeededOrLog(args3->get_FailureSourceModulePath(&failedModule)) ? wide_to_utf8(failedModule.get()) : std::optional{}, + frameInfos + ); + channelDelegate->onProcessFailed(std::move(detail)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ProcessFailed_HResult); + + wil::com_ptr webView2; + if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView2)))) { + auto add_DOMContentLoaded_HResult = webView2->add_DOMContentLoaded( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args) + { + if (channelDelegate) { + channelDelegate->onProgressChanged(66); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_DOMContentLoaded_HResult); + } + + wil::com_ptr webView4; + if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView4)))) { + auto add_FrameCreated_HResult = webView4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) + { + wil::com_ptr frame; + wil::com_ptr frame2; + if (succeededOrLog(args->get_Frame(&frame)) && SUCCEEDED(frame->QueryInterface(IID_PPV_ARGS(&frame2)))) { + auto frame_add_WebMessageReceived_HResult = frame2->add_WebMessageReceived(Callback( + [this](ICoreWebView2Frame* sender, ICoreWebView2WebMessageReceivedEventArgs* args) + { + return this->onCallJsHandler(false, args); + }).Get(), nullptr); + failedLog(frame_add_WebMessageReceived_HResult); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_FrameCreated_HResult); + + auto add_DownloadStarting_HResult = webView4->add_DownloadStarting( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2DownloadStartingEventArgs* args) + { + wil::com_ptr deferral; + wil::com_ptr download; + if (channelDelegate && settings->useOnDownloadStart && succeededOrLog(args->get_DownloadOperation(&download)) && succeededOrLog(args->GetDeferral(&deferral))) { + + wil::unique_cotaskmem_string uri; + std::string url = SUCCEEDED(download->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : ""; + + INT64 contentLength = 0; + failedLog(download->get_TotalBytesToReceive(&contentLength)); + + wil::unique_cotaskmem_string downloadMimeType; + std::optional mimeType = SUCCEEDED(download->get_MimeType(&downloadMimeType)) ? wide_to_utf8(downloadMimeType.get()) : std::optional{}; + + wil::unique_cotaskmem_string downloadContentDisposition; + std::optional contentDisposition = SUCCEEDED(download->get_ContentDisposition(&downloadContentDisposition)) ? wide_to_utf8(downloadContentDisposition.get()) : std::optional{}; + + wil::unique_cotaskmem_string resultFilePath; + std::optional suggestedFilename = SUCCEEDED(download->get_ContentDisposition(&resultFilePath)) ? wide_to_utf8(resultFilePath.get()) : std::optional{}; + + auto request = std::make_shared( + contentDisposition, + contentLength, + mimeType, + suggestedFilename, + url + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + failedLog(args->put_Handled(response->handled)); + auto resultFilePath = response->resultFilePath; + if (resultFilePath.has_value()) { + failedLog(args->put_ResultFilePath(utf8_to_wide(resultFilePath.value()).c_str())); + } + auto action = response->action; + if (action.has_value()) { + switch (action.value()) { + case DownloadStartResponseAction::cancel: + failedLog(args->put_Cancel(true)); + break; + } + } + failedLog(deferral->Complete()); + return false; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onDownloadStarting(std::move(request), std::move(callback)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_DownloadStarting_HResult); + } + + if (auto webView5 = webView.try_query()) { + auto add_ClientCertificateRequested_HResult = webView5->add_ClientCertificateRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2ClientCertificateRequestedEventArgs* args) + { + wil::com_ptr deferral; + wil::unique_cotaskmem_string host; + + if (channelDelegate && plugin && plugin->inAppWebViewManager && + succeededOrLog(args->get_Host(&host)) && succeededOrLog(args->GetDeferral(&deferral))) { + + std::vector> mutuallyTrustedCertificates = {}; + wil::com_ptr certificateCollection; + uint32_t certCount = 0; + if (succeededOrLog(args->get_MutuallyTrustedCertificates(&certificateCollection)) && succeededOrLog(certificateCollection->get_Count(&certCount))) { + + for (uint32_t i = 0; i < certCount; i++) { + wil::com_ptr clientCert; + if (succeededOrLog(certificateCollection->GetValueAtIndex(i, &clientCert))) { + wil::unique_cotaskmem_string certPemEncoded; + if (succeededOrLog(clientCert->ToPemEncoding(&certPemEncoded))) { + mutuallyTrustedCertificates.push_back(std::make_shared(wide_to_utf8(certPemEncoded.get()))); + } + } + } + } + + std::vector allowedCertificateAuthorities = {}; + wil::com_ptr authoritiesCollection; + uint32_t authoritiesCount = 0; + if (succeededOrLog(args->get_AllowedCertificateAuthorities(&authoritiesCollection)) && succeededOrLog(authoritiesCollection->get_Count(&authoritiesCount))) { + for (uint32_t i = 0; i < authoritiesCount; i++) { + wil::unique_cotaskmem_string authority; + if (succeededOrLog(authoritiesCollection->GetValueAtIndex(i, &authority))) { + allowedCertificateAuthorities.push_back(base64_decode(wide_to_utf8(authority.get()))); + } + } + } + + args->get_AllowedCertificateAuthorities(&authoritiesCollection); + + int port = 0; + args->get_Port(&port); + + BOOL isProxy = false; + args->get_IsProxy(&isProxy); + + std::string scheme = ""; + auto currentUrl = getUrl(); + if (currentUrl.has_value()) { + scheme = currentUrl.value().substr(0, currentUrl.value().find(':')); + } + + auto protectionSpace = std::make_unique( + wide_to_utf8(host.get()), + scheme, + std::optional{}, + port, + std::optional>{}, + std::optional>{} + ); + auto challenge = std::make_unique( + std::move(protectionSpace), + allowedCertificateAuthorities, + isProxy, + mutuallyTrustedCertificates + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, certCount, certificateCollection, args](const std::shared_ptr response) + { + auto action = response->action; + + if (action.has_value()) { + switch (action.value()) { + case ClientCertResponseAction::proceed: + if (certCount > 0 && response->selectedCertificate >= 0) { + wil::com_ptr selectedClientCert; + if (succeededOrLog(certificateCollection->GetValueAtIndex((uint32_t)response->selectedCertificate, &selectedClientCert))) { + args->put_SelectedCertificate(selectedClientCert.get()); + } + } + args->put_Handled(true); + args->put_Cancel(false); + break; + case ClientCertResponseAction::ignore: + args->put_Handled(true); + args->put_Cancel(false); + break; + case ClientCertResponseAction::cancel: + default: + args->put_Cancel(true); + break; + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedClientCertRequest(std::move(challenge), std::move(callback)); + } + return S_OK; + }) + .Get(), nullptr); + failedLog(add_ClientCertificateRequested_HResult); + } + + if (auto webView10 = webView.try_query()) { + auto add_BasicAuthenticationRequested_HResult = webView10->add_BasicAuthenticationRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2BasicAuthenticationRequestedEventArgs* args) + { + wil::com_ptr deferral; + wil::com_ptr basicAuthenticationResponse; + wil::unique_cotaskmem_string url; + wil::unique_cotaskmem_string realmChallenge; + + if (channelDelegate && plugin && plugin->inAppWebViewManager && + succeededOrLog(args->get_Uri(&url)) && succeededOrLog(args->get_Challenge(&realmChallenge)) && + succeededOrLog(args->get_Response(&basicAuthenticationResponse)) && succeededOrLog(args->GetDeferral(&deferral))) { + + previousAuthRequestFailureCount++; + + try { + winrt::Windows::Foundation::Uri const uri{ url.get() }; + + auto basicRealm = std::string{ "Basic realm=\"" }; + auto basicRealmLength = basicRealm.length(); + auto realm = wide_to_utf8(realmChallenge.get()); + if (starts_with(realm, basicRealm)) { + realm = realm.substr(basicRealmLength, realm.length() - basicRealmLength - 1); + } + + auto protectionSpace = std::make_unique( + wide_to_utf8(uri.Host().c_str()), + wide_to_utf8(uri.SchemeName().c_str()), + realm, + uri.Port(), + std::optional>{}, + std::optional>{} + ); + auto challenge = std::make_unique( + std::move(protectionSpace), + previousAuthRequestFailureCount, + std::optional>{} + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, basicAuthenticationResponse, args](const std::shared_ptr response) + { + auto action = response->action; + std::wstring username = utf8_to_wide(response->username); + std::wstring password = utf8_to_wide(response->password); + + if (action.has_value()) { + switch (action.value()) { + case HttpAuthResponseAction::proceed: + failedLog(basicAuthenticationResponse->put_UserName(username.c_str())); + failedLog(basicAuthenticationResponse->put_Password(password.c_str())); + break; + case HttpAuthResponseAction::cancel: + default: + args->put_Cancel(true); + break; + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedHttpAuthRequest(std::move(challenge), std::move(callback)); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } + } + return S_OK; + }) + .Get(), nullptr); + failedLog(add_BasicAuthenticationRequested_HResult); + } + + if (auto webView14 = webView.try_query()) { + auto add_ServerCertificateErrorDetected_HResult = webView14->add_ServerCertificateErrorDetected( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ServerCertificateErrorDetectedEventArgs* args) + { + wil::com_ptr deferral; + wil::unique_cotaskmem_string requestUrl; + if (succeededOrLog(args->get_RequestUri(&requestUrl)) && succeededOrLog(args->GetDeferral(&deferral))) { + + wil::com_ptr serverCert; + auto sslCert = std::optional>{}; + if (succeededOrLog(args->get_ServerCertificate(&serverCert))) { + wil::unique_cotaskmem_string certPemEncoded; + if (succeededOrLog(serverCert->ToPemEncoding(&certPemEncoded))) { + sslCert = std::make_shared(wide_to_utf8(certPemEncoded.get())); + } + } + + auto sslError = std::optional>{}; + COREWEBVIEW2_WEB_ERROR_STATUS errorStatus; + if (succeededOrLog(args->get_ErrorStatus(&errorStatus))) { + sslError = std::make_shared( + errorStatus, + COREWEBVIEW2_WEB_ERROR_STATUS_ToString(errorStatus) + ); + } + + try { + winrt::Windows::Foundation::Uri const uri{ requestUrl.get() }; + + auto protectionSpace = std::make_unique( + wide_to_utf8(uri.Host().c_str()), + wide_to_utf8(uri.SchemeName().c_str()), + std::optional{}, + uri.Port(), + sslCert, + sslError + ); + auto challenge = std::make_unique( + std::move(protectionSpace) + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + auto action = response->action; + + if (action.has_value()) { + switch (action.value()) { + case ServerTrustAuthResponseAction::proceed: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW); + break; + case ServerTrustAuthResponseAction::cancel: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_CANCEL); + break; + default: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_DEFAULT); + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedServerTrustAuthRequest(std::move(challenge), std::move(callback)); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ServerCertificateErrorDetected_HResult); + } + + if (userContentController) { + userContentController->registerEventHandlers(); + } + } + + void InAppWebView::registerSurfaceEventHandlers() + { + if (!webViewCompositionController) { + return; + } + + failedLog(webViewCompositionController->add_CursorChanged( + Callback( + [this](ICoreWebView2CompositionController* sender, + IUnknown* args) -> HRESULT + { + HCURSOR cursor; + if (cursorChangedCallback_ && + sender->get_Cursor(&cursor) == S_OK) { + cursorChangedCallback_(cursor); + } + return S_OK; + }) + .Get(), nullptr)); + } + + std::optional InAppWebView::getUrl() const + { + wil::unique_cotaskmem_string uri; + return webView && succeededOrLog(webView->get_Source(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + } + + std::optional InAppWebView::getTitle() const + { + wil::unique_cotaskmem_string title; + return webView && succeededOrLog(webView->get_DocumentTitle(&title)) ? wide_to_utf8(title.get()) : std::optional{}; + } + + void InAppWebView::loadUrl(const std::shared_ptr urlRequest) const + { + if (!webView || !urlRequest->url.has_value()) { + return; + } + + std::wstring url = utf8_to_wide(urlRequest->url.value()); + + wil::com_ptr webViewEnv2; + wil::com_ptr webView2; + if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv2))) && SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView2)))) { + wil::com_ptr webResourceRequest; + std::wstring method = urlRequest->method.has_value() ? utf8_to_wide(urlRequest->method.value()) : L"GET"; + + wil::com_ptr postDataStream = nullptr; + if (urlRequest->body.has_value()) { + auto postData = std::string(urlRequest->body->begin(), urlRequest->body->end()); + postDataStream = SHCreateMemStream( + reinterpret_cast(postData.data()), static_cast(postData.length())); + } + if (succeededOrLog(webViewEnv2->CreateWebResourceRequest( + url.c_str(), + method.c_str(), + postDataStream.get(), + L"", + &webResourceRequest + ))) { + wil::com_ptr requestHeaders; + if (SUCCEEDED(webResourceRequest->get_Headers(&requestHeaders))) { + if (method.compare(L"GET") != 0) { + requestHeaders->SetHeader(L"Flutter-InAppWebView-Request-Method", method.c_str()); + } + if (urlRequest->headers.has_value()) { + auto& headers = urlRequest->headers.value(); + for (auto const& [key, val] : headers) { + requestHeaders->SetHeader(utf8_to_wide(key).c_str(), utf8_to_wide(val).c_str()); + } + } + } + failedLog(webView2->NavigateWithWebResourceRequest(webResourceRequest.get())); + } + return; + } + failedLog(webView->Navigate(url.c_str())); + } + + + void InAppWebView::loadFile(const std::string& assetFilePath) const + { + if (!webView) { + return; + } + + WCHAR* buf = new WCHAR[32768]; + GetModuleFileName(NULL, buf, 32768); + std::filesystem::path exeAbsPath = std::wstring(buf); + delete[] buf; + + std::filesystem::path flutterAssetPath("data/flutter_assets/" + assetFilePath); + auto absAssetFilePath = exeAbsPath.parent_path() / flutterAssetPath; + + if (!std::filesystem::exists(absAssetFilePath)) { + debugLog(absAssetFilePath.native() + L" asset file cannot be found!"); + return; + } + failedLog(webView->Navigate(absAssetFilePath.c_str())); + } + + void InAppWebView::loadData(const std::string& data) const + { + if (!webView) { + return; + } + + failedLog(webView->NavigateToString(utf8_to_wide(data).c_str())); + } + + void InAppWebView::reload() const + { + if (!webView) { + return; + } + + failedLog(webView->Reload()); + } + + void InAppWebView::goBack() + { + if (!webView) { + return; + } + + failedLog(webView->GoBack()); + } + + bool InAppWebView::canGoBack() const + { + BOOL canGoBack_; + return webView && succeededOrLog(webView->get_CanGoBack(&canGoBack_)) ? canGoBack_ : false; + } + + void InAppWebView::goForward() + { + if (!webView) { + return; + } + + failedLog(webView->GoForward()); + } + + bool InAppWebView::canGoForward() const + { + BOOL canGoForward_; + return webView && succeededOrLog(webView->get_CanGoForward(&canGoForward_)) ? canGoForward_ : false; + } + + void InAppWebView::goBackOrForward(const int64_t& steps) + { + getCopyBackForwardList( + [this, steps](std::unique_ptr webHistory) + { + if (webHistory && webHistory->currentIndex.has_value() && webHistory->list.has_value()) { + auto currentIndex = webHistory->currentIndex.value(); + auto items = &webHistory->list.value(); + auto nextIndex = currentIndex + steps; + int64_t size = items->size(); + if (nextIndex >= 0 && nextIndex < size) { + auto entryId = items->at(nextIndex)->entryId; + if (entryId.has_value()) { + failedAndLog(webView->CallDevToolsProtocolMethod(L"Page.navigateToHistoryEntry", utf8_to_wide("{\"entryId\": " + std::to_string(entryId.value()) + "}").c_str(), Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + } + } + } + } + ); + } + + void InAppWebView::canGoBackOrForward(const int64_t& steps, std::function completionHandler) const + { + getCopyBackForwardList( + [steps, completionHandler](std::unique_ptr webHistory) + { + auto canGoBackOrForward_ = false; + if (webHistory && webHistory->currentIndex.has_value() && webHistory->list.has_value()) { + auto currentIndex = webHistory->currentIndex.value(); + auto items = &webHistory->list.value(); + auto nextIndex = currentIndex + steps; + int64_t size = items->size(); + canGoBackOrForward_ = nextIndex >= 0 && nextIndex < size; + } + + if (completionHandler) { + completionHandler(canGoBackOrForward_); + } + } + ); + } + + void InAppWebView::stopLoading() const + { + if (!webView) { + return; + } + + failedLog(webView->Stop()); + } + + void InAppWebView::getCopyBackForwardList(const std::function)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(std::make_unique(std::nullopt, std::nullopt)); + } + return; + } + + failedLog(webView->CallDevToolsProtocolMethod(L"Page.getNavigationHistory", L"{}", Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (!completionHandler) { + return S_OK; + } + + if (errorCode == S_OK) { + auto historyJson = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + + int64_t currentIndex = historyJson.at("currentIndex").is_number_unsigned() ? historyJson.at("currentIndex").get() : 0; + std::vector entries = historyJson.at("entries").is_array() ? historyJson.at("entries").get>() : std::vector{}; + + std::vector> webHistoryItems; + webHistoryItems.reserve(entries.size()); + int64_t i = 0; + for (auto const& entry : entries) { + int64_t offset = i - currentIndex; + webHistoryItems.push_back(std::make_shared( + entry.at("id").is_number_integer() ? entry.at("id").get() : std::optional{}, + i, + offset, + entry.at("userTypedURL").is_string() ? entry.at("userTypedURL").get() : std::optional{}, + entry.at("title").is_string() ? entry.at("title").get() : std::optional{}, + entry.at("url").is_string() ? entry.at("url").get() : std::optional{} + )); + i++; + } + + completionHandler(std::make_unique(currentIndex, webHistoryItems)); + } + else { + debugLog(errorCode); + completionHandler(std::make_unique(std::nullopt, std::nullopt)); + } + + return S_OK; + } + ).Get())); + } + + void InAppWebView::evaluateJavascript(const std::string& source, const std::shared_ptr contentWorld, const std::function completionHandler) const + { + if (!webView || !userContentController) { + if (completionHandler) { + completionHandler("null"); + } + return; + } + + userContentController->createContentWorld(contentWorld, + [=](const int& contextId) + { + nlohmann::json parameters = { + {"expression", source}, + { "returnByValue", true } + }; + + if (contextId >= 0) { + parameters["contextId"] = contextId; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Runtime.evaluate", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + nlohmann::json result; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + result = json["result"].contains("value") ? json["result"]["value"] : nlohmann::json{}; + if (json.contains("exceptionDetails")) { + nlohmann::json exceptionDetails = json["exceptionDetails"]; + auto errorMessage = exceptionDetails.contains("exception") && exceptionDetails["exception"].contains("value") + ? exceptionDetails["exception"]["value"].dump() : + (result["value"].is_null() ? exceptionDetails["text"].get() : result["value"].dump()); + result = nlohmann::json{}; + debugLog(exceptionDetails.dump()); + if (channelDelegate) { + channelDelegate->onConsoleMessage(errorMessage, 3); + } + } + } + if (completionHandler) { + completionHandler(result.dump()); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler("null"); + } + }); + } + + void InAppWebView::callAsyncJavaScript(const std::string& functionBody, const std::string& argumentsAsJson, const std::shared_ptr contentWorld, const std::function completionHandler) const + { + if (!webView || !userContentController) { + if (completionHandler) { + completionHandler("null"); + } + return; + } + + userContentController->createContentWorld(contentWorld, + [=](const int& contextId) + { + std::vector functionArgumentNamesList; + std::vector functionArgumentValuesList; + + auto jsonVal = nlohmann::json::parse(argumentsAsJson); + for (auto const& [key, val] : jsonVal.items()) { + functionArgumentNamesList.push_back(key); + functionArgumentValuesList.push_back(val.dump()); + } + + auto source = replace_all_copy(CALL_ASYNC_JAVASCRIPT_WRAPPER_JS, VAR_FUNCTION_ARGUMENT_NAMES, join(functionArgumentNamesList, ", ")); + replace_all(source, VAR_FUNCTION_ARGUMENT_VALUES, join(functionArgumentValuesList, ", ")); + replace_all(source, VAR_FUNCTION_BODY, functionBody); + + nlohmann::json parameters = { + {"expression", source}, + {"awaitPromise", true}, + { "returnByValue", true } + }; + + if (contextId >= 0) { + parameters["contextId"] = contextId; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Runtime.evaluate", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + nlohmann::json result = { + {"value", nlohmann::json{}}, + {"error", nlohmann::json{}} + }; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + result["value"] = json["result"].contains("value") ? json["result"]["value"] : nlohmann::json{}; + if (json.contains("exceptionDetails")) { + nlohmann::json exceptionDetails = json["exceptionDetails"]; + auto errorMessage = exceptionDetails.contains("exception") && exceptionDetails["exception"].contains("value") + ? exceptionDetails["exception"]["value"].dump() : + (result["value"].is_null() ? exceptionDetails["text"].get() : result["value"].dump()); + result["value"] = nlohmann::json{}; + result["error"] = errorMessage; + debugLog(exceptionDetails.dump()); + if (channelDelegate) { + channelDelegate->onConsoleMessage(errorMessage, 3); + } + } + } + if (completionHandler) { + completionHandler(result.dump()); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler("null"); + } + }); + } + + void InAppWebView::addUserScript(const std::shared_ptr userScript) const + { + if (!userContentController) { + return; + } + + userContentController->addUserOnlyScript(userScript); + } + + void InAppWebView::removeUserScript(const int64_t index, const std::shared_ptr userScript) const + { + if (!userContentController) { + return; + } + + userContentController->removeUserOnlyScriptAt(index, userScript->injectionTime); + } + + void InAppWebView::removeUserScriptsByGroupName(const std::string& groupName) const + { + if (!userContentController) { + return; + } + + userContentController->removeUserOnlyScriptsByGroupName(groupName); + } + + void InAppWebView::removeAllUserScripts() const + { + if (!userContentController) { + return; + } + + userContentController->removeAllUserOnlyScripts(); + } + + void InAppWebView::takeScreenshot(const std::optional> screenshotConfiguration, const std::function)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(std::nullopt); + } + return; + } + + nlohmann::json parameters = {}; + if (screenshotConfiguration.has_value()) { + auto& scp = screenshotConfiguration.value(); + parameters["format"] = to_lowercase_copy(CompressFormatToString(scp->compressFormat)); + if (scp->compressFormat == CompressFormat::jpeg) { + parameters["quality"] = scp->quality; + } + if (scp->rect.has_value()) { + auto& rect = scp->rect.value(); + parameters["clip"] = { + {"x", rect->x}, + {"y", rect->y}, + {"width", rect->width}, + {"height", rect->height}, + {"scale", scaleFactor_} + }; + } + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Page.captureScreenshot", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::optional result = std::nullopt; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + result = json["data"].get(); + } + if (completionHandler) { + completionHandler(result); + } + return S_OK; + } + ).Get()); + if (failedAndLog(hr) && completionHandler) { + completionHandler(std::nullopt); + } + } + + void InAppWebView::setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap) + { + wil::com_ptr webView2Settings; + if (succeededOrLog(webView->get_Settings(&webView2Settings))) { + if (fl_map_contains_not_null(newSettingsMap, "javaScriptEnabled") && settings->javaScriptEnabled != newSettings->javaScriptEnabled) { + webView2Settings->put_IsScriptEnabled(newSettings->javaScriptEnabled); + } + + if (fl_map_contains_not_null(newSettingsMap, "supportZoom") && settings->supportZoom != newSettings->supportZoom) { + webView2Settings->put_IsZoomControlEnabled(newSettings->supportZoom); + } + + if (fl_map_contains_not_null(newSettingsMap, "isInspectable") && settings->isInspectable != newSettings->isInspectable) { + webView2Settings->put_AreDevToolsEnabled(newSettings->isInspectable); + } + + if (fl_map_contains_not_null(newSettingsMap, "disableContextMenu") && settings->disableContextMenu != newSettings->disableContextMenu) { + webView2Settings->put_AreDefaultContextMenusEnabled(!newSettings->disableContextMenu); + } + + if (fl_map_contains_not_null(newSettingsMap, "disableDefaultErrorPage") && settings->disableDefaultErrorPage != newSettings->disableDefaultErrorPage) { + webView2Settings->put_IsBuiltInErrorPageEnabled(!newSettings->disableDefaultErrorPage); + } + + if (fl_map_contains_not_null(newSettingsMap, "statusBarEnabled") && settings->statusBarEnabled != newSettings->statusBarEnabled) { + webView2Settings->put_IsStatusBarEnabled(newSettings->statusBarEnabled); + } + + if (auto webView2Settings2 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "userAgent") && !string_equals(settings->userAgent, newSettings->userAgent)) { + webView2Settings2->put_UserAgent(utf8_to_wide(newSettings->userAgent).c_str()); + } + } + + if (auto webView2Settings3 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "browserAcceleratorKeysEnabled") && settings->browserAcceleratorKeysEnabled != newSettings->browserAcceleratorKeysEnabled) { + webView2Settings3->put_AreBrowserAcceleratorKeysEnabled(newSettings->browserAcceleratorKeysEnabled); + } + } + + if (auto webView2Settings4 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "generalAutofillEnabled") && settings->generalAutofillEnabled != newSettings->generalAutofillEnabled) { + webView2Settings4->put_IsGeneralAutofillEnabled(newSettings->generalAutofillEnabled); + } + if (fl_map_contains_not_null(newSettingsMap, "passwordAutosaveEnabled") && settings->passwordAutosaveEnabled != newSettings->passwordAutosaveEnabled) { + webView2Settings4->put_IsPasswordAutosaveEnabled(newSettings->passwordAutosaveEnabled); + } + } + + if (auto webView2Settings5 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "pinchZoomEnabled") && settings->pinchZoomEnabled != newSettings->pinchZoomEnabled) { + webView2Settings5->put_IsPinchZoomEnabled(newSettings->pinchZoomEnabled); + } + } + + if (auto webView2Settings6 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "allowsBackForwardNavigationGestures") && settings->allowsBackForwardNavigationGestures != newSettings->allowsBackForwardNavigationGestures) { + webView2Settings6->put_IsSwipeNavigationEnabled(newSettings->allowsBackForwardNavigationGestures); + } + } + + if (auto webView2Settings7 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "hiddenPdfToolbarItems") && settings->hiddenPdfToolbarItems != newSettings->hiddenPdfToolbarItems) { + webView2Settings7->put_HiddenPdfToolbarItems((COREWEBVIEW2_PDF_TOOLBAR_ITEMS)newSettings->hiddenPdfToolbarItems); + } + } + + if (auto webView2Settings8 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "reputationCheckingRequired") && settings->reputationCheckingRequired != newSettings->reputationCheckingRequired) { + webView2Settings8->put_IsReputationCheckingRequired(newSettings->reputationCheckingRequired); + } + } + + if (auto webView2Settings9 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "nonClientRegionSupportEnabled") && settings->nonClientRegionSupportEnabled != newSettings->nonClientRegionSupportEnabled) { + webView2Settings9->put_IsNonClientRegionSupportEnabled(newSettings->nonClientRegionSupportEnabled); + } + } + } + + if (auto webViewController2 = webViewController.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "transparentBackground") && settings->transparentBackground != newSettings->transparentBackground) { + BYTE alpha = newSettings->transparentBackground ? 0 : 255; + webViewController2->put_DefaultBackgroundColor({ alpha, 255, 255, 255 }); + } + } + + settings = newSettings; + } + + flutter::EncodableValue InAppWebView::getSettings() const + { + if (!settings || !webView) { + return make_fl_value(); + } + + return settings->getRealSettings(this); + } + + void InAppWebView::openDevTools() const + { + if (!webView) { + return; + } + + failedLog(webView->OpenDevToolsWindow()); + } + + void InAppWebView::callDevToolsProtocolMethod(const std::string& methodName, const std::optional& parametersAsJson, const std::function&)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(S_OK, std::nullopt); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod( + utf8_to_wide(methodName).c_str(), + !parametersAsJson.has_value() || parametersAsJson.value().empty() ? L"{}" : utf8_to_wide(parametersAsJson.value()).c_str(), + Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + if (completionHandler) { + completionHandler(errorCode, wide_to_utf8(returnObjectAsJson)); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(hr, std::nullopt); + } + } + + void InAppWebView::addDevToolsProtocolEventListener(const std::string& eventName) + { + if (map_contains(devToolsProtocolEventListener_, eventName)) { + return; + } + + wil::com_ptr eventReceiver; + if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(utf8_to_wide(eventName).c_str(), &eventReceiver))) { + EventRegistrationToken token = {}; + auto hr = eventReceiver->add_DevToolsProtocolEventReceived( + Callback( + [this, eventName]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + failedLog(args->get_ParameterObjectAsJson(&json)); + channelDelegate->onDevToolsProtocolEventReceived(eventName, wide_to_utf8(json.get())); + + return S_OK; + }) + .Get(), &token); + if (succeededOrLog(hr)) { + devToolsProtocolEventListener_.insert({ eventName, std::make_pair(std::move(eventReceiver), token) }); + } + } + } + + void InAppWebView::removeDevToolsProtocolEventListener(const std::string& eventName) + { + if (map_contains(devToolsProtocolEventListener_, eventName)) { + auto eventReceiver = devToolsProtocolEventListener_.at(eventName).first; + auto token = devToolsProtocolEventListener_.at(eventName).second; + eventReceiver->remove_DevToolsProtocolEventReceived(token); + devToolsProtocolEventListener_.erase(eventName); + } + } + + + void InAppWebView::pause() const + { + wil::com_ptr webView3; + if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView3))) && succeededOrLog(webViewController->put_IsVisible(false))) { + failedLog(webView3->TrySuspend(Callback( + [this](HRESULT errorCode, BOOL isSuccessful) -> HRESULT + { + failedLog(errorCode); + return S_OK; + }) + .Get())); + } + } + + void InAppWebView::resume() const + { + wil::com_ptr webView3; + if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView3))) && succeededOrLog(webViewController->put_IsVisible(true))) { + failedLog(webView3->Resume()); + } + } + + + void InAppWebView::getCertificate(const std::function>)> completionHandler) const + { + auto url = getUrl(); + if (!webView || !url.has_value()) { + if (completionHandler) { + completionHandler(std::nullopt); + } + return; + } + + nlohmann::json parameters = { + {"origin", url.value()} + }; + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.getCertificate", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::optional> result = std::nullopt; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + if (json.at("tableNames").is_array()) { + auto tableNames = json.at("tableNames").get>(); + if (tableNames.size() > 0) { + result = std::make_unique(tableNames.at(0)); + } + } + } + if (completionHandler) { + completionHandler(std::move(result)); + } + return S_OK; + } + ).Get()); + if (failedAndLog(hr) && completionHandler) { + completionHandler(std::nullopt); + } + } + + void InAppWebView::clearSslPreferences(const std::function completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(); + } + return; + } + + if (auto webView14 = webView.try_query()) { + auto hr = webView14->ClearServerCertificateErrorActions(Callback( + [completionHandler](HRESULT errorCode) + { + failedAndLog(errorCode); + if (completionHandler) { + completionHandler(); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(); + } + return; + } + + if (completionHandler) { + completionHandler(); + } + } + + bool InAppWebView::isInterfaceSupported(const std::string& interfaceName) const + { + if (!webView) { + return false; + } + + if (string_equals(interfaceName, "ICoreWebView2") || starts_with(interfaceName, std::string{ "ICoreWebView2_" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_2"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_3"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_4"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_5"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_6"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_7"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_8"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_9"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_10"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_11"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_12"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_13"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_14"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_15"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_16"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_17"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_18"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_19"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_20"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_21"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_22"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_23"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_24"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_25"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_26"): + return webView.try_query() != nullptr; + default: + return false; + } + } + + wil::com_ptr webView2Settings; + if (succeededOrLog(webView->get_Settings(&webView2Settings))) { + if (starts_with(interfaceName, std::string{ "ICoreWebView2Settings" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Settings"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings2"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings3"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings4"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings5"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings6"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings7"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings8"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings9"): + return webView2Settings.try_query() != nullptr; + default: + return false; + } + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Controller" }) && webViewController) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Controller"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller2"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller3"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller4"): + return webViewController.try_query() != nullptr; + default: + return false; + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2CompositionController" }) && webViewCompositionController) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2CompositionController"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController2"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController3"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController4"): + return webViewCompositionController.try_query() != nullptr; + default: + return false; + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Environment" }) && webViewEnv) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Environment"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment2"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment3"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment4"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment5"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment6"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment7"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment8"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment9"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment10"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment11"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment12"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment13"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment14"): + return webViewEnv.try_query() != nullptr; + default: + return false; + } + } + + return false; + } + + double InAppWebView::getZoomScale() const + { + return zoomScaleFactor_; + } + + // flutter_view + void InAppWebView::setSurfaceSize(size_t width, size_t height, float scale_factor) + { + if (!webViewController) { + return; + } + + if (surface_ && width > 0 && height > 0) { + scaleFactor_ = scale_factor; + auto scaled_width = width * scale_factor; + auto scaled_height = height * scale_factor; + + RECT bounds; + bounds.left = 0; + bounds.top = 0; + bounds.right = static_cast(scaled_width); + bounds.bottom = static_cast(scaled_height); + + surface_->put_Size({ scaled_width, scaled_height }); + + wil::com_ptr webViewController3; + if (SUCCEEDED(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_RasterizationScale(scale_factor); + } + + if (webViewController->put_Bounds(bounds) != S_OK) { + std::cerr << "Setting webview bounds failed." << std::endl; + } + + if (surfaceSizeChangedCallback_) { + surfaceSizeChangedCallback_(width, height); + } + } + } + + void InAppWebView::setPosition(size_t x, size_t y, float scale_factor) + { + if (!webViewController || !plugin || !plugin->registrar) { + return; + } + + if (x >= 0 && y >= 0) { + scaleFactor_ = scale_factor; + auto scaled_x = static_cast(x * scale_factor); + auto scaled_y = static_cast(y * scale_factor); + + auto titleBarHeight = ((GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME)) * scale_factor) + GetSystemMetrics(SM_CXPADDEDBORDER); + auto borderWidth = (GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXPADDEDBORDER)) * scale_factor; + + RECT flutterWindowRect; + HWND flutterWindowHWnd = plugin->registrar->GetView()->GetNativeWindow(); + GetWindowRect(flutterWindowHWnd, &flutterWindowRect); + + HWND webViewHWnd; + if (succeededOrLog(webViewController->get_ParentWindow(&webViewHWnd))) { + ::SetWindowPos(webViewHWnd, + nullptr, + static_cast(flutterWindowRect.left + scaled_x - borderWidth), + static_cast(flutterWindowRect.top + scaled_y - titleBarHeight), + 0, 0, + SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + } + } + } + + void InAppWebView::setCursorPos(double x, double y) + { + if (!webViewCompositionController) { + return; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + lastCursorPos_ = point; + + // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.774.44 + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND::COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE, + virtualKeys_.state(), 0, point); + } + + void InAppWebView::setPointerUpdate(int32_t pointer, + InAppWebViewPointerEventKind eventKind, double x, + double y, double size, double pressure) + { + if (!webViewEnv || !webViewCompositionController) { + return; + } + + COREWEBVIEW2_POINTER_EVENT_KIND event = + COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + UINT32 pointerFlags = POINTER_FLAG_NONE; + switch (eventKind) { + case InAppWebViewPointerEventKind::Activate: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ACTIVATE; + break; + case InAppWebViewPointerEventKind::Down: + event = COREWEBVIEW2_POINTER_EVENT_KIND_DOWN; + pointerFlags = + POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + case InAppWebViewPointerEventKind::Enter: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ENTER; + break; + case InAppWebViewPointerEventKind::Leave: + event = COREWEBVIEW2_POINTER_EVENT_KIND_LEAVE; + break; + case InAppWebViewPointerEventKind::Up: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UP; + pointerFlags = POINTER_FLAG_UP; + break; + case InAppWebViewPointerEventKind::Update: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + pointerFlags = + POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + + RECT rect; + rect.left = point.x - 2; + rect.right = point.x + 2; + rect.top = point.y - 2; + rect.bottom = point.y + 2; + + wil::com_ptr webViewEnv3; + if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv3)))) { + wil::com_ptr pInfo; + if (SUCCEEDED(webViewEnv3->CreateCoreWebView2PointerInfo(&pInfo))) { + if (pInfo) { + pInfo->put_PointerId(pointer); + pInfo->put_PointerKind(PT_TOUCH); + pInfo->put_PointerFlags(pointerFlags); + pInfo->put_TouchFlags(TOUCH_FLAG_NONE); + pInfo->put_TouchMask(TOUCH_MASK_CONTACTAREA | TOUCH_MASK_PRESSURE); + pInfo->put_TouchPressure( + std::clamp((UINT32)(pressure == 0.0 ? 1024 : 1024 * pressure), + (UINT32)0, (UINT32)1024)); + pInfo->put_PixelLocationRaw(point); + pInfo->put_TouchContactRaw(rect); + webViewCompositionController->SendPointerInput(event, pInfo.get()); + } + } + } + } + + void InAppWebView::setPointerButtonState(InAppWebViewPointerEventKind kind, InAppWebViewPointerButton button) + { + if (!webViewCompositionController) { + return; + } + + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS eventVirtualKeys_ = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; + COREWEBVIEW2_MOUSE_EVENT_KIND eventKind; + UINT32 mouseData = 0; + POINT point = { 0, 0 };; + + switch (kind) { + case InAppWebViewPointerEventKind::Down: + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN; + break; + default: + eventKind = static_cast(0); + } + eventVirtualKeys_ = virtualKeys_.state(); + point = lastCursorPos_; + break; + case InAppWebViewPointerEventKind::Up: + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + break; + default: + eventKind = static_cast(0); + } + eventVirtualKeys_ = virtualKeys_.state(); + point = lastCursorPos_; + break; + case InAppWebViewPointerEventKind::Leave: + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEAVE; + break; + default: + eventKind = static_cast(0); + } + + webViewCompositionController->SendMouseInput(eventKind, eventVirtualKeys_, mouseData, point); + } + + void InAppWebView::sendScroll(double delta, bool horizontal) + { + if (!webViewCompositionController) { + return; + } + + auto offset = static_cast(delta * settings->scrollMultiplier); + + if (horizontal) { + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL, virtualKeys_.state(), + offset, lastCursorPos_); + } + else { + webViewCompositionController->SendMouseInput(COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL, + virtualKeys_.state(), offset, + lastCursorPos_); + } + } + + void InAppWebView::setScrollDelta(double delta_x, double delta_y) + { + if (!webViewCompositionController) { + return; + } + + if (delta_x != 0.0) { + sendScroll(delta_x, true); + } + if (delta_y != 0.0) { + sendScroll(delta_y, false); + } + } + + bool InAppWebView::createSurface(const HWND parentWindow, + winrt::com_ptr compositor) + { + if (!webViewCompositionController || !webViewController) { + return false; + } + + winrt::com_ptr root; + if (FAILED(compositor->CreateContainerVisual(root.put()))) { + return false; + } + surface_ = root.try_as(); + assert(surface_); + + // initial size. doesn't matter as we resize the surface anyway. + surface_->put_Size({ 1280, 720 }); + surface_->put_IsVisible(true); + + winrt::com_ptr webview_visual; + compositor->CreateContainerVisual( + reinterpret_cast( + webview_visual.put())); + + auto webview_visual2 = + webview_visual.try_as(); + if (webview_visual2) { + webview_visual2->put_RelativeSizeAdjustment({ 1.0f, 1.0f }); + } + + winrt::com_ptr children; + root->get_Children(children.put()); + children->InsertAtTop(webview_visual.get()); + webViewCompositionController->put_RootVisualTarget(webview_visual2.get()); + + webViewController->put_IsVisible(true); + + return true; + } + + bool InAppWebView::isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus) + { + return webErrorStatus >= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT && webErrorStatus <= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID; + } + + HRESULT InAppWebView::onCallJsHandler(const bool& isMainFrame, ICoreWebView2WebMessageReceivedEventArgs* args) + { + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_WebMessageAsJson(&json))) { + nlohmann::basic_json<> message; + try { + message = nlohmann::json::parse(wide_to_utf8(json.get())); + } + catch (nlohmann::json::parse_error& ex) { + debugLog("Error parsing JSON message of callHandler method: " + std::string(ex.what())); + return S_OK; + } + + if (message.is_object() && message.contains("name") && message.at("name").is_string() && message.contains("body") && message.at("body").is_object()) { + auto name = message.at("name").get(); + auto body = message.at("body").get(); + + if (name.compare("callHandler") == 0) { + if (!body.contains("handlerName") || !body.at("handlerName").is_string()) { + debugLog("handlerName is null or undefined"); + return S_OK; + } + + auto handlerName = body.at("handlerName").get(); + auto bridgeSecret = body.contains("_bridgeSecret") && body.at("_bridgeSecret").is_string() ? body.at("_bridgeSecret").get() : ""; + auto callHandlerID = body.contains("_callHandlerID") && body.at("_callHandlerID").is_number_integer() ? body.at("_callHandlerID").get() : 0; + auto origin = body.contains("origin") && body.at("origin").is_string() ? body.at("origin").get() : ""; + auto requestUrl = body.contains("requestUrl") && body.at("requestUrl").is_string() ? body.at("requestUrl").get() : ""; + auto handlerArgs = body.contains("args") && body.at("args").is_string() ? body.at("args").get() : ""; + + wil::unique_cotaskmem_string sourceUrl; + if (succeededOrLog(args->get_Source(&sourceUrl))) { + requestUrl = wide_to_utf8(sourceUrl.get()); + origin = get_origin_from_url(requestUrl); + } + + if (!string_equals(expectedBridgeSecret, bridgeSecret)) { + debugLog("Bridge access attempt with wrong secret token, possibly from malicious code from origin: " + origin); + return S_OK; + } + + bool isOriginAllowed = false; + if (settings->javaScriptHandlersOriginAllowList.has_value()) { + for (auto& allowedOrigin : settings->javaScriptHandlersOriginAllowList.value()) { + if (std::regex_match(origin, std::regex(allowedOrigin))) { + isOriginAllowed = true; + break; + } + } + } + else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true; + } + if (!isOriginAllowed) { + debugLog("Bridge access attempt from an origin not allowed: " + origin); + return S_OK; + } + + if (settings->javaScriptHandlersForMainFrameOnly && !isMainFrame) { + debugLog("Bridge access attempt from a sub-frame origin: " + origin); + return S_OK; + } + + /* + boolean isInternalHandler = true; + switch (handlerName) { + default: + isInternalHandler = false; + break; + } + + if (isInternalHandler) { + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].resolve(); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + return S_OK; + } + */ + + auto callback = std::make_unique(); + callback->defaultBehaviour = [this, callHandlerID](const std::optional response) + { + std::string json = "null"; + if (response.has_value() && !response.value()->IsNull()) { + json = std::get(*(response.value())); + } + + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].resolve(" + json + "); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + callback->error = [this, callHandlerID](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + auto errorMessage = error_code + ", " + error_message; + debugLog(errorMessage); + + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].reject(new Error('" + replace_all_copy(errorMessage, "\'", "\\'") + "')); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + + auto data = std::make_unique(origin, requestUrl, isMainFrame, handlerArgs); + channelDelegate->onCallJsHandler(handlerName, std::move(data), std::move(callback)); + } + } + else { + debugLog("Invalid JSON message of callHandler method"); + } + } + + return S_OK; + } + + InAppWebView::~InAppWebView() + { + debugLog("dealloc InAppWebView"); + userContentController = nullptr; + if (webView) { + failedLog(webView->Stop()); + } + HWND parentWindow = nullptr; + if (webViewCompositionController && webViewController && succeededOrLog(webViewController->get_ParentWindow(&parentWindow))) { + // if it's an InAppWebView (so webViewCompositionController will be not a nullptr!), + // then destroy the Window created with it + DestroyWindow(parentWindow); + } + if (webViewController) { + failedLog(webViewController->Close()); + } + navigationActions_.clear(); + inAppBrowser = nullptr; + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h new file mode 100644 index 000000000..b7b27861c --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h @@ -0,0 +1,214 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ + +#include +#include +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../plugin_scripts_js/plugin_scripts_util.h" +#include "../types/content_world.h" +#include "../types/navigation_action.h" +#include "../types/screenshot_configuration.h" +#include "../types/ssl_certificate.h" +#include "../types/url_request.h" +#include "../types/web_history.h" +#include "../utils/uuid.h" +#include "../webview_environment/webview_environment.h" +#include "in_app_webview_settings.h" +#include "user_content_controller.h" +#include "webview_channel_delegate.h" + +#include + +namespace flutter_inappwebview_plugin +{ + class InAppBrowser; + + using namespace Microsoft::WRL; + + // custom_platform_view + enum class InAppWebViewPointerButton { None, Primary, Secondary, Tertiary }; + enum class InAppWebViewPointerEventKind { Activate, Down, Enter, Leave, Up, Update, Cancel }; + typedef std::function + SurfaceSizeChangedCallback; + typedef std::function CursorChangedCallback; + struct VirtualKeyState { + public: + inline void setIsLeftButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON, + is_down); + } + + inline void setIsRightButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON, + is_down); + } + + inline void setIsMiddleButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON, + is_down); + } + + inline COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state() const { return state_; } + + private: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state_ = + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; + + inline void set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS key, bool flag) + { + if (flag) { + state_ |= key; + } + else { + state_ &= ~key; + } + } + }; + + const std::string CALL_ASYNC_JAVASCRIPT_WRAPPER_JS = "(async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") { \ + " + VAR_FUNCTION_BODY + " \ + })(" + VAR_FUNCTION_ARGUMENT_VALUES + ");"; + + struct InAppWebViewCreationParams { + const std::variant id; + const std::shared_ptr initialSettings; + const std::optional>> initialUserScripts; + }; + + class InAppWebView + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::variant id; + wil::com_ptr webViewEnv; + wil::com_ptr webViewController; + wil::com_ptr webViewCompositionController; + wil::com_ptr webView; + std::unique_ptr channelDelegate; + std::shared_ptr settings; + InAppBrowser* inAppBrowser = nullptr; + std::unique_ptr userContentController; + + InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); + InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); + ~InAppWebView(); + + static void createInAppWebViewEnv(const HWND parentWindow, const bool& willBeSurface, WebViewEnvironment* webViewEnvironment, const std::shared_ptr initialSettings, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler); + + // custom_platform_view + ABI::Windows::UI::Composition::IVisual* const surface() + { + return surface_.get(); + } + void setSurfaceSize(size_t width, size_t height, float scale_factor); + void setPosition(size_t x, size_t y, float scale_factor); + void setCursorPos(double x, double y); + void setPointerUpdate(int32_t pointer, InAppWebViewPointerEventKind eventKind, + double x, double y, double size, double pressure); + void setPointerButtonState(InAppWebViewPointerEventKind kind, InAppWebViewPointerButton button); + void sendScroll(double offset, bool horizontal); + void setScrollDelta(double delta_x, double delta_y); + void onSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surfaceSizeChangedCallback_ = std::move(callback); + } + void onCursorChanged(CursorChangedCallback callback) + { + cursorChangedCallback_ = std::move(callback); + } + bool createSurface(const HWND parentWindow, + winrt::com_ptr compositor); + + void initChannel(const std::optional> viewId, const std::optional channelName); + void prepare(const InAppWebViewCreationParams& params); + std::optional getUrl() const; + std::optional getTitle() const; + void loadUrl(const std::shared_ptr urlRequest) const; + void loadFile(const std::string& assetFilePath) const; + void loadData(const std::string& data) const; + void reload() const; + void goBack(); + bool canGoBack() const; + void goForward(); + bool canGoForward() const; + void goBackOrForward(const int64_t& steps); + void canGoBackOrForward(const int64_t& steps, std::function completionHandler) const; + bool isLoading() const + { + return isLoading_; + } + void stopLoading() const; + void evaluateJavascript(const std::string& source, const std::shared_ptr contentWorld, const std::function completionHandler) const; + void callAsyncJavaScript(const std::string& functionBody, const std::string& argumentsAsJson, const std::shared_ptr contentWorld, const std::function completionHandler) const; + void getCopyBackForwardList(const std::function)> completionHandler) const; + void addUserScript(const std::shared_ptr userScript) const; + void removeUserScript(const int64_t index, const std::shared_ptr userScript) const; + void removeUserScriptsByGroupName(const std::string& groupName) const; + void removeAllUserScripts() const; + void takeScreenshot(const std::optional> screenshotConfiguration, const std::function)> completionHandler) const; + void setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap); + flutter::EncodableValue getSettings() const; + void openDevTools() const; + void callDevToolsProtocolMethod(const std::string& methodName, const std::optional& parametersAsJson, const std::function&)> completionHandler) const; + void addDevToolsProtocolEventListener(const std::string& eventName); + void removeDevToolsProtocolEventListener(const std::string& eventName); + void pause() const; + void resume() const; + void getCertificate(const std::function>)> completionHandler) const; + void clearSslPreferences(const std::function completionHandler) const; + bool isInterfaceSupported(const std::string& interfaceName) const; + double getZoomScale() const; + + std::string pageFrameId() const + { + return pageFrameId_; + } + + static bool isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus); + private: + // custom_platform_view + winrt::com_ptr surface_; + SurfaceSizeChangedCallback surfaceSizeChangedCallback_; + CursorChangedCallback cursorChangedCallback_; + float scaleFactor_ = 1.0; + POINT lastCursorPos_ = { 0, 0 }; + VirtualKeyState virtualKeys_; + + const std::string expectedBridgeSecret = get_uuid(); + bool javaScriptBridgeEnabled = true; + std::map> navigationActions_ = {}; + std::shared_ptr lastNavigationAction_; + bool isLoading_ = false; + std::string pageFrameId_; + std::map, EventRegistrationToken>> devToolsProtocolEventListener_ = {}; + int64_t previousAuthRequestFailureCount = 0; + double zoomScaleFactor_ = 1.0; + + void registerEventHandlers(); + void registerSurfaceEventHandlers(); + HRESULT onCallJsHandler(const bool& isMainFrame, ICoreWebView2WebMessageReceivedEventArgs* args); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp new file mode 100644 index 000000000..8a03dbd33 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../types/url_request.h" +#include "../types/user_script.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "../webview_environment/webview_environment_manager.h" +#include "in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + InAppWebViewManager::InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), InAppWebViewManager::METHOD_CHANNEL_NAME) + { + if (!rohelper_) { + rohelper_ = std::make_unique(RO_INIT_SINGLETHREADED); + + if (rohelper_->WinRtAvailable()) { + DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, DQTAT_COM_STA }; + + if (FAILED(rohelper_->CreateDispatcherQueueController( + options, dispatcher_queue_controller_.put()))) { + std::cerr << "Creating DispatcherQueueController failed." << std::endl; + return; + } + + if (!isGraphicsCaptureSessionSupported()) { + std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " + "supported." + << std::endl; + return; + } + + graphics_context_ = std::make_unique(rohelper_.get()); + compositor_ = graphics_context_->CreateCompositor(); + if (compositor_) { + // fix for KernelBase.dll RaiseFailFastException + // when app is closing + compositor_->AddRef(); + } + valid_ = graphics_context_->IsValid(); + } + } + + windowClass_.lpszClassName = CustomPlatformView::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + } + + void InAppWebViewManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "createInAppWebView")) { + if (isSupported()) { + createInAppWebView(arguments, std::move(result)); + } + else { + result->Error("0", "Creating an InAppWebView instance is not supported! Graphics Context is not valid!"); + } + } + else if (string_equals(methodName, "dispose")) { + auto id = get_fl_map_value(*arguments, "id"); + if (map_contains(webViews, (uint64_t)id)) { + auto platformView = webViews.at(id).get(); + if (platformView) { + platformView->UnregisterMethodCallHandler(); + } + webViews.erase(id); + } + result->Success(); + } + else if (string_equals(methodName, "disposeKeepAlive")) { + auto keepAliveId = get_fl_map_value(*arguments, "keepAliveId"); + disposeKeepAlive(keepAliveId); + result->Success(); + } + else if (string_equals(methodName, "setJavaScriptBridgeName")) { + auto bridgeName = get_fl_map_value(*arguments, "bridgeName"); + JavaScriptBridgeJS::set_JAVASCRIPT_BRIDGE_NAME(bridgeName); + result->Success(); + } + else if (string_equals(methodName, "getJavaScriptBridgeName")) { + result->Success(JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME()); + } + else { + result->NotImplemented(); + } + } + + void InAppWebViewManager::createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + if (!plugin) { + result_->Error("0", "Cannot create the InAppWebView instance!"); + return; + } + + auto settingsMap = get_fl_map_value(*arguments, "initialSettings"); + auto urlRequestMap = get_optional_fl_map_value(*arguments, "initialUrlRequest"); + auto initialFile = get_optional_fl_map_value(*arguments, "initialFile"); + auto initialDataMap = get_optional_fl_map_value(*arguments, "initialData"); + auto initialUserScriptList = get_optional_fl_map_value(*arguments, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(*arguments, "webViewEnvironmentId"); + auto keepAliveId = get_optional_fl_map_value(*arguments, "keepAliveId"); + auto windowId = get_optional_fl_map_value(*arguments, "windowId"); + + RECT bounds; + GetClientRect(plugin->registrar->GetView()->GetNativeWindow(), &bounds); + + auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0, + 0, bounds.right - bounds.left, bounds.bottom - bounds.top, + plugin->registrar->GetView()->GetNativeWindow(), + nullptr, + windowClass_.hInstance, nullptr); + + if (keepAliveId.has_value() && map_contains(keepAliveWebViews, keepAliveId.value())) { + auto webView = std::move(keepAliveWebViews.at(keepAliveId.value())->view); + keepAliveWebViews.erase(keepAliveId.value()); + auto customPlatformView = std::make_unique(plugin->registrar->messenger(), + plugin->registrar->texture_registrar(), + graphics_context(), + hwnd, + std::move(webView)); + auto textureId = customPlatformView->texture_id(); + keepAliveWebViews.insert({ keepAliveId.value(), std::move(customPlatformView) }); + result_->Success(textureId); + return; + } + + auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + auto initialSettings = std::make_shared(settingsMap); + + InAppWebView::createInAppWebViewEnv(hwnd, true, webViewEnvironment, initialSettings, + [=](wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + { + if (plugin && webViewEnv && webViewController && webViewCompositionController) { + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppWebViewCreationParams params = { + "", + std::move(initialSettings), + initialUserScripts + }; + + auto inAppWebView = std::make_unique(plugin, params, hwnd, std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController)); + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + if (urlRequest.has_value()) { + inAppWebView->loadUrl(urlRequest.value()); + } + else if (initialFile.has_value()) { + inAppWebView->loadFile(initialFile.value()); + } + else if (initialDataMap.has_value()) { + inAppWebView->loadData(get_fl_map_value(initialDataMap.value(), "data")); + } + + if (windowId.has_value() && map_contains(windowWebViews, windowId.value())) { + auto windowWebViewArgs = windowWebViews.at(windowId.value()).get(); + windowWebViewArgs->args->put_NewWindow(inAppWebView->webView.get()); + windowWebViewArgs->args->put_Handled(TRUE); + windowWebViewArgs->deferral->Complete(); + windowWebViews.erase(windowId.value()); + } + + auto customPlatformView = std::make_unique(plugin->registrar->messenger(), + plugin->registrar->texture_registrar(), + graphics_context(), + hwnd, + std::move(inAppWebView)); + + auto textureId = customPlatformView->texture_id(); + + if (keepAliveId.has_value()) { + customPlatformView->view->initChannel(keepAliveId.value(), std::nullopt); + keepAliveWebViews.insert({ keepAliveId.value(), std::move(customPlatformView) }); + } + else { + customPlatformView->view->initChannel(textureId, std::nullopt); + webViews.insert({ textureId, std::move(customPlatformView) }); + } + result_->Success(textureId); + } + else { + result_->Error("0", "Cannot create the InAppWebView instance!"); + } + } + ); + } + + void InAppWebViewManager::disposeKeepAlive(const std::string& keepAliveId) + { + if (map_contains(keepAliveWebViews, keepAliveId)) { + auto platformView = keepAliveWebViews.at(keepAliveId).get(); + if (platformView) { + platformView->UnregisterMethodCallHandler(); + } + keepAliveWebViews.erase(keepAliveId); + } + } + + bool InAppWebViewManager::isGraphicsCaptureSessionSupported() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession, + &className, &classNameHeader))) { + return false; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics* + capture_session_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics), + (void**)&capture_session_statics))) { + return false; + } + + boolean is_supported = false; + if (FAILED(capture_session_statics->IsSupported(&is_supported))) { + return false; + } + + return !!is_supported; + } + + InAppWebViewManager::~InAppWebViewManager() + { + debugLog("dealloc InAppWebViewManager"); + webViews.clear(); + keepAliveWebViews.clear(); + windowWebViews.clear(); + UnregisterClass(windowClass_.lpszClassName, nullptr); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h new file mode 100644 index 000000000..b11a38074 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h @@ -0,0 +1,64 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../custom_platform_view/custom_platform_view.h" +#include "../custom_platform_view/graphics_context.h" +#include "../custom_platform_view/util/rohelper.h" +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "../types/new_window_requested_args.h" +#include "windows.ui.composition.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebViewManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViews; + std::map> keepAliveWebViews; + std::map> windowWebViews; + int64_t windowAutoincrementId = 0; + + bool isSupported() const { return valid_; } + bool isGraphicsCaptureSessionSupported(); + GraphicsContext* graphics_context() const + { + return graphics_context_.get(); + }; + rx::RoHelper* rohelper() const { return rohelper_.get(); } + winrt::com_ptr compositor() const + { + return compositor_; + } + + InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~InAppWebViewManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result); + void disposeKeepAlive(const std::string& keepAliveId); + private: + inline static std::shared_ptr rohelper_ = nullptr; + inline static winrt::com_ptr + dispatcher_queue_controller_; + inline static std::unique_ptr graphics_context_ = nullptr; + inline static winrt::com_ptr compositor_; + WNDCLASS windowClass_ = {}; + inline static bool valid_ = false; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp new file mode 100644 index 000000000..7a2e5218d --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp @@ -0,0 +1,192 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "in_app_webview.h" +#include "in_app_webview_settings.h" + +#include + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + InAppWebViewSettings::InAppWebViewSettings() {}; + + InAppWebViewSettings::InAppWebViewSettings(const flutter::EncodableMap& encodableMap) + { + useShouldOverrideUrlLoading = get_fl_map_value(encodableMap, "useShouldOverrideUrlLoading", useShouldOverrideUrlLoading); + useOnLoadResource = get_fl_map_value(encodableMap, "useOnLoadResource", useOnLoadResource); + useOnDownloadStart = get_fl_map_value(encodableMap, "useOnDownloadStart", useOnDownloadStart); + useShouldInterceptRequest = get_fl_map_value(encodableMap, "useShouldInterceptRequest", useShouldInterceptRequest); + userAgent = get_fl_map_value(encodableMap, "userAgent", userAgent); + javaScriptEnabled = get_fl_map_value(encodableMap, "javaScriptEnabled", javaScriptEnabled); + transparentBackground = get_fl_map_value(encodableMap, "transparentBackground", transparentBackground); + supportZoom = get_fl_map_value(encodableMap, "supportZoom", supportZoom); + isInspectable = get_fl_map_value(encodableMap, "isInspectable", isInspectable); + disableContextMenu = get_fl_map_value(encodableMap, "disableContextMenu", disableContextMenu); + incognito = get_fl_map_value(encodableMap, "incognito", incognito); + if (fl_map_contains_not_null(encodableMap, "javaScriptHandlersOriginAllowList")) { + javaScriptHandlersOriginAllowList = get_optional_fl_map_value>(encodableMap, "javaScriptHandlersOriginAllowList"); + } + javaScriptHandlersForMainFrameOnly = get_fl_map_value(encodableMap, "javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly); + javaScriptBridgeEnabled = get_fl_map_value(encodableMap, "javaScriptBridgeEnabled", javaScriptBridgeEnabled); + if (fl_map_contains_not_null(encodableMap, "javaScriptBridgeOriginAllowList")) { + javaScriptBridgeOriginAllowList = get_optional_fl_map_value>(encodableMap, "javaScriptBridgeOriginAllowList"); + } + if (fl_map_contains_not_null(encodableMap, "javaScriptBridgeForMainFrameOnly")) { + javaScriptBridgeForMainFrameOnly = get_fl_map_value(encodableMap, "javaScriptBridgeForMainFrameOnly"); + } + if (fl_map_contains_not_null(encodableMap, "pluginScriptsOriginAllowList")) { + pluginScriptsOriginAllowList = get_optional_fl_map_value>(encodableMap, "pluginScriptsOriginAllowList"); + } + pluginScriptsForMainFrameOnly = get_fl_map_value(encodableMap, "pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly); + scrollMultiplier = get_fl_map_value(encodableMap, "scrollMultiplier", scrollMultiplier); + disableDefaultErrorPage = get_fl_map_value(encodableMap, "disableDefaultErrorPage", disableDefaultErrorPage); + statusBarEnabled = get_fl_map_value(encodableMap, "statusBarEnabled", statusBarEnabled); + browserAcceleratorKeysEnabled = get_fl_map_value(encodableMap, "browserAcceleratorKeysEnabled", browserAcceleratorKeysEnabled); + generalAutofillEnabled = get_fl_map_value(encodableMap, "generalAutofillEnabled", generalAutofillEnabled); + passwordAutosaveEnabled = get_fl_map_value(encodableMap, "passwordAutosaveEnabled", passwordAutosaveEnabled); + pinchZoomEnabled = get_fl_map_value(encodableMap, "pinchZoomEnabled", pinchZoomEnabled); + allowsBackForwardNavigationGestures = get_fl_map_value(encodableMap, "allowsBackForwardNavigationGestures", allowsBackForwardNavigationGestures); + hiddenPdfToolbarItems = get_fl_map_value(encodableMap, "hiddenPdfToolbarItems", hiddenPdfToolbarItems); + reputationCheckingRequired = get_fl_map_value(encodableMap, "reputationCheckingRequired", reputationCheckingRequired); + nonClientRegionSupportEnabled = get_fl_map_value(encodableMap, "nonClientRegionSupportEnabled", nonClientRegionSupportEnabled); + handleAcceleratorKeyPressed = get_fl_map_value(encodableMap, "handleAcceleratorKeyPressed", handleAcceleratorKeyPressed); + } + + flutter::EncodableMap InAppWebViewSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"useShouldOverrideUrlLoading", useShouldOverrideUrlLoading}, + {"useOnLoadResource", useOnLoadResource}, + {"useOnDownloadStart", useOnDownloadStart}, + {"useShouldInterceptRequest", useShouldInterceptRequest}, + {"userAgent", userAgent}, + {"javaScriptEnabled", javaScriptEnabled}, + {"transparentBackground", transparentBackground}, + {"supportZoom", supportZoom}, + {"isInspectable", isInspectable}, + {"disableContextMenu", disableContextMenu}, + {"incognito", incognito}, + {"javaScriptHandlersOriginAllowList", make_fl_value(javaScriptHandlersOriginAllowList)}, + {"javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly}, + {"javaScriptBridgeEnabled", javaScriptBridgeEnabled}, + {"javaScriptBridgeOriginAllowList", make_fl_value(javaScriptBridgeOriginAllowList)}, + {"javaScriptBridgeForMainFrameOnly", make_fl_value(javaScriptBridgeForMainFrameOnly)}, + {"pluginScriptsOriginAllowList", make_fl_value(pluginScriptsOriginAllowList)}, + {"pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly}, + {"scrollMultiplier", scrollMultiplier}, + {"disableDefaultErrorPage", disableDefaultErrorPage}, + {"statusBarEnabled", statusBarEnabled}, + {"browserAcceleratorKeysEnabled", browserAcceleratorKeysEnabled}, + {"generalAutofillEnabled", generalAutofillEnabled}, + {"passwordAutosaveEnabled", passwordAutosaveEnabled}, + {"pinchZoomEnabled", pinchZoomEnabled}, + {"allowsBackForwardNavigationGestures", allowsBackForwardNavigationGestures}, + {"hiddenPdfToolbarItems", hiddenPdfToolbarItems}, + {"reputationCheckingRequired", reputationCheckingRequired}, + {"nonClientRegionSupportEnabled", nonClientRegionSupportEnabled}, + {"handleAcceleratorKeyPressed", handleAcceleratorKeyPressed} + }; + } + + flutter::EncodableMap InAppWebViewSettings::getRealSettings(const InAppWebView* inAppWebView) const + { + auto settingsMap = toEncodableMap(); + if (inAppWebView && inAppWebView->webView) { + auto webView = inAppWebView->webView; + wil::com_ptr settings; + if (succeededOrLog(webView->get_Settings(&settings))) { + BOOL realJavaScriptEnabled; + if (SUCCEEDED(settings->get_IsScriptEnabled(&realJavaScriptEnabled))) { + settingsMap["javaScriptEnabled"] = (bool)realJavaScriptEnabled; + } + BOOL realSupportZoom; + if (SUCCEEDED(settings->get_IsZoomControlEnabled(&realSupportZoom))) { + settingsMap["supportZoom"] = (bool)realSupportZoom; + } + BOOL realIsInspectable; + if (SUCCEEDED(settings->get_AreDevToolsEnabled(&realIsInspectable))) { + settingsMap["isInspectable"] = (bool)realIsInspectable; + } + BOOL areDefaultContextMenusEnabled; + if (SUCCEEDED(settings->get_AreDefaultContextMenusEnabled(&areDefaultContextMenusEnabled))) { + settingsMap["disableContextMenu"] = !(bool)areDefaultContextMenusEnabled; + } + BOOL isBuiltInErrorPageEnabled; + if (SUCCEEDED(settings->get_IsBuiltInErrorPageEnabled(&isBuiltInErrorPageEnabled))) { + settingsMap["disableDefaultErrorPage"] = !(bool)isBuiltInErrorPageEnabled; + } + BOOL isStatusBarEnabled; + if (SUCCEEDED(settings->get_IsBuiltInErrorPageEnabled(&isStatusBarEnabled))) { + settingsMap["statusBarEnabled"] = (bool)isStatusBarEnabled; + } + + if (auto settings2 = settings.try_query()) { + wil::unique_cotaskmem_string realUserAgent; + if (SUCCEEDED(settings2->get_UserAgent(&realUserAgent))) { + settingsMap["userAgent"] = wide_to_utf8(realUserAgent.get()); + } + } + + if (auto settings3 = settings.try_query()) { + BOOL areBrowserAcceleratorKeysEnabled; + if (SUCCEEDED(settings3->get_AreBrowserAcceleratorKeysEnabled(&areBrowserAcceleratorKeysEnabled))) { + settingsMap["browserAcceleratorKeysEnabled"] = (bool)areBrowserAcceleratorKeysEnabled; + } + } + + if (auto settings4 = settings.try_query()) { + BOOL isGeneralAutofillEnabled; + if (SUCCEEDED(settings4->get_IsGeneralAutofillEnabled(&isGeneralAutofillEnabled))) { + settingsMap["generalAutofillEnabled"] = (bool)isGeneralAutofillEnabled; + } + BOOL isPasswordAutosaveEnabled; + if (SUCCEEDED(settings4->get_IsPasswordAutosaveEnabled(&isPasswordAutosaveEnabled))) { + settingsMap["passwordAutosaveEnabled"] = (bool)isPasswordAutosaveEnabled; + } + } + + if (auto settings5 = settings.try_query()) { + BOOL isPinchZoomEnabled; + if (SUCCEEDED(settings5->get_IsPinchZoomEnabled(&isPinchZoomEnabled))) { + settingsMap["pinchZoomEnabled"] = (bool)isPinchZoomEnabled; + } + } + + if (auto settings6 = settings.try_query()) { + BOOL isSwipeNavigationEnabled; + if (SUCCEEDED(settings6->get_IsSwipeNavigationEnabled(&isSwipeNavigationEnabled))) { + settingsMap["allowsBackForwardNavigationGestures"] = (bool)isSwipeNavigationEnabled; + } + } + + if (auto settings7 = settings.try_query()) { + COREWEBVIEW2_PDF_TOOLBAR_ITEMS realHiddenPdfToolbarItems; + if (SUCCEEDED(settings7->get_HiddenPdfToolbarItems(&realHiddenPdfToolbarItems))) { + settingsMap["hiddenPdfToolbarItems"] = (int64_t)realHiddenPdfToolbarItems; + } + } + + if (auto settings8 = settings.try_query()) { + BOOL isReputationCheckingRequired; + if (SUCCEEDED(settings8->get_IsReputationCheckingRequired(&isReputationCheckingRequired))) { + settingsMap["reputationCheckingRequired"] = (bool)isReputationCheckingRequired; + } + } + + if (auto settings9 = settings.try_query()) { + BOOL isNonClientRegionSupportEnabled; + if (SUCCEEDED(settings9->get_IsNonClientRegionSupportEnabled(&isNonClientRegionSupportEnabled))) { + settingsMap["nonClientRegionSupportEnabled"] = (bool)isNonClientRegionSupportEnabled; + } + } + } + } + return settingsMap; + } + + InAppWebViewSettings::~InAppWebViewSettings() + { + debugLog("dealloc InAppWebViewSettings"); + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h new file mode 100644 index 000000000..7eab9ea23 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h @@ -0,0 +1,54 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class InAppWebView; + + class InAppWebViewSettings + { + public: + bool useShouldOverrideUrlLoading = false; + bool useOnLoadResource = false; + bool useOnDownloadStart = false; + bool useShouldInterceptRequest = false; + std::string userAgent; + bool javaScriptEnabled = true; + bool transparentBackground = false; + bool supportZoom = true; + bool isInspectable = true; + bool disableContextMenu = false; + bool incognito = false; + std::optional> javaScriptHandlersOriginAllowList = std::optional>{}; + bool javaScriptHandlersForMainFrameOnly = false; + bool javaScriptBridgeEnabled = true; + std::optional> javaScriptBridgeOriginAllowList = std::optional>{}; + std::optional javaScriptBridgeForMainFrameOnly = std::optional{}; + std::optional> pluginScriptsOriginAllowList = std::optional>{}; + bool pluginScriptsForMainFrameOnly = false; + int64_t scrollMultiplier = 1; + bool disableDefaultErrorPage = false; + bool statusBarEnabled = true; + bool browserAcceleratorKeysEnabled = true; + bool generalAutofillEnabled = true; + bool passwordAutosaveEnabled = false; + bool pinchZoomEnabled = true; + bool allowsBackForwardNavigationGestures = true; + int64_t hiddenPdfToolbarItems = COREWEBVIEW2_PDF_TOOLBAR_ITEMS::COREWEBVIEW2_PDF_TOOLBAR_ITEMS_NONE; + bool reputationCheckingRequired = true; + bool nonClientRegionSupportEnabled = false; + bool handleAcceleratorKeyPressed = false; + + InAppWebViewSettings(); + InAppWebViewSettings(const flutter::EncodableMap& encodableMap); + ~InAppWebViewSettings(); + + flutter::EncodableMap toEncodableMap() const; + flutter::EncodableMap getRealSettings(const InAppWebView* inAppWebView) const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp new file mode 100644 index 000000000..35e7284c5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp @@ -0,0 +1,469 @@ +#include +#include + +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "in_app_webview.h" +#include "user_content_controller.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + UserContentController::UserContentController(InAppWebView* webView) + : webView_(webView) + {} + + void UserContentController::registerEventHandlers() + { + if (!webView_ || !(webView_->webView)) { + return; + } + + wil::com_ptr executionContextCreated; + if (succeededOrLog(webView_->webView->GetDevToolsProtocolEventReceiver(L"Runtime.executionContextCreated", &executionContextCreated))) { + auto hr = executionContextCreated->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + nlohmann::json context = nlohmann::json::parse(wide_to_utf8(json.get()))["context"]; + auto id = context["id"].get(); + auto name = context["name"].get(); + nlohmann::json auxData = context["auxData"]; + auto isDefault = auxData["isDefault"].get(); + auto frameId = auxData["frameId"].get(); + if (string_equals(webView_->pageFrameId(), frameId)) { + if (isDefault) { + contentWorlds_.insert_or_assign(ContentWorld::page()->name, id); + } + else { + contentWorlds_.insert_or_assign(name, id); + addPluginScriptsIfRequired(std::make_shared(name)); + } + } + } + + return S_OK; + }) + .Get(), nullptr); + + failedLog(hr); + } + + /* + wil::com_ptr executionContextDestroyed; + if (succeededOrLog(webView_->webView->GetDevToolsProtocolEventReceiver(L"Runtime.executionContextDestroyed ", &executionContextDestroyed))) { + failedLog(executionContextDestroyed->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + debugLog("executionContextDestroyed: " + wide_to_utf8(json.get())); + } + + return S_OK; + }) + .Get(), nullptr)); + } + */ + } + + std::vector> UserContentController::getUserOnlyScriptsAt(const UserScriptInjectionTime& injectionTime) const + { + return userOnlyScripts_.at(injectionTime); + } + + void UserContentController::addUserOnlyScript(std::shared_ptr userScript) + { + if (!userScript) { + return; + } + + addPluginScriptsIfRequired(userScript->contentWorld); + addScriptToWebView(userScript, nullptr); + userOnlyScripts_.at(userScript->injectionTime).push_back(std::move(userScript)); + } + + void UserContentController::addUserOnlyScripts(std::vector> userScripts) + { + for (auto& userScript : userScripts) { + addUserOnlyScript(std::move(userScript)); + } + } + + void UserContentController::removeUserOnlyScript(std::shared_ptr userScript) + { + if (!userScript) { + return; + } + + if (webView_) { + removeScriptFromWebView(userScript, nullptr); + } + + vector_remove_erase(userOnlyScripts_.at(userScript->injectionTime), std::move(userScript)); + } + + void UserContentController::removeUserOnlyScriptAt(const int64_t& index, const UserScriptInjectionTime& injectionTime) + { + auto& vec = userOnlyScripts_.at(injectionTime); + int64_t size = vec.size(); + if (index >= size) { + return; + } + + auto& userScript = vec.at(index); + if (userScript) { + removeScriptFromWebView(userScript, nullptr); + vec.erase(vec.begin() + index); + } + } + + void UserContentController::removeAllUserOnlyScripts() + { + auto& userScriptsAtStart = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart); + auto& userScriptsAtEnd = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + if (webView_) { + for (auto& userScript : userScriptsAtStart) { + removeScriptFromWebView(userScript, nullptr); + } + for (auto& userScript : userScriptsAtEnd) { + removeScriptFromWebView(userScript, nullptr); + } + } + + userScriptsAtStart.clear(); + userScriptsAtEnd.clear(); + } + + void UserContentController::removeUserOnlyScriptsByGroupName(const std::string& groupName) + { + std::vector> userScriptsAtStart = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> userScriptsAtEnd = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& userScript : userScriptsAtStart) { + if (string_equals(groupName, userScript->groupName)) { + removeUserOnlyScript(userScript); + } + } + + for (auto& userScript : userScriptsAtEnd) { + if (string_equals(groupName, userScript->groupName)) { + removeUserOnlyScript(userScript); + } + } + } + + std::vector> UserContentController::getPluginScriptsAt(const UserScriptInjectionTime& injectionTime) const + { + return pluginScripts_.at(injectionTime); + } + + bool UserContentController::containsUserOnlyScript(std::shared_ptr userScript) const + { + if (!userScript) { + return false; + } + + return vector_contains(userOnlyScripts_.at(userScript->injectionTime), std::move(userScript)); + } + + bool UserContentController::containsUserOnlyScriptByGroupName(const std::string& groupName) const + { + return vector_contains_if(userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart), [groupName](const std::shared_ptr userScript) { return string_equals(groupName, userScript->groupName); }) || + vector_contains_if(userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd), [groupName](const std::shared_ptr userScript) { return string_equals(groupName, userScript->groupName); }); + } + + void UserContentController::addPluginScript(std::shared_ptr pluginScript) + { + if (!pluginScript) { + return; + } + + addScriptToWebView(pluginScript, nullptr); + pluginScripts_.at(pluginScript->injectionTime).push_back(std::move(pluginScript)); + } + + void UserContentController::addPluginScripts(std::vector> pluginScripts) + { + for (auto& pluginScript : pluginScripts) { + addPluginScript(std::move(pluginScript)); + } + } + + void UserContentController::removePluginScript(std::shared_ptr pluginScript) + { + if (!pluginScript) { + return; + } + + if (webView_) { + removeScriptFromWebView(pluginScript, nullptr); + } + + vector_remove_erase(pluginScripts_.at(pluginScript->injectionTime), std::move(pluginScript)); + } + + void UserContentController::removeAllPluginScripts() + { + auto& pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + auto& pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + if (webView_) { + for (auto& pluginScript : pluginScriptsAtStart) { + removeScriptFromWebView(pluginScript, nullptr); + } + for (auto& pluginScript : pluginScriptsAtEnd) { + removeScriptFromWebView(pluginScript, nullptr); + } + } + + pluginScriptsAtStart.clear(); + pluginScriptsAtEnd.clear(); + } + + bool UserContentController::containsPluginScript(std::shared_ptr pluginScript) const + { + if (!pluginScript) { + return false; + } + + auto injectionTime = pluginScript->injectionTime; + return vector_contains(pluginScripts_.at(injectionTime), std::move(pluginScript)); + } + + + bool UserContentController::containsPluginScript(std::shared_ptr pluginScript, const std::shared_ptr contentWorld) const + { + if (!pluginScript || !map_contains(pluginScriptsInContentWorlds_, contentWorld->name)) { + return false; + } + + return vector_contains(pluginScriptsInContentWorlds_.at(contentWorld->name), std::move(pluginScript)); + } + + bool UserContentController::containsPluginScriptByGroupName(const std::string& groupName) const + { + return vector_contains_if(pluginScripts_.at(UserScriptInjectionTime::atDocumentStart), [groupName](const std::shared_ptr pluginScript) { return string_equals(groupName, pluginScript->groupName); }) || + vector_contains_if(pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd), [groupName](const std::shared_ptr pluginScript) { return string_equals(groupName, pluginScript->groupName); }); + } + + void UserContentController::removePluginScriptsByGroupName(const std::string& groupName) + { + std::vector> pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& pluginScript : pluginScriptsAtStart) { + if (string_equals(groupName, pluginScript->groupName)) { + removePluginScript(pluginScript); + } + } + + for (auto& pluginScript : pluginScriptsAtEnd) { + if (string_equals(groupName, pluginScript->groupName)) { + removePluginScript(pluginScript); + } + } + } + + + std::vector> UserContentController::getPluginScriptsRequiredInAllContentWorlds() const + { + std::vector> res; + + std::vector> pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& pluginScript : pluginScriptsAtStart) { + if (!pluginScript->contentWorld && pluginScript->isRequiredInAllContentWorlds()) { + res.push_back(pluginScript); + } + } + + for (auto& pluginScript : pluginScriptsAtEnd) { + if (!pluginScript->contentWorld && pluginScript->isRequiredInAllContentWorlds()) { + res.push_back(pluginScript); + } + } + + return res; + } + + void UserContentController::createContentWorld(const std::shared_ptr contentWorld, const std::function completionHandler) + { + if (!webView_ || !(webView_->webView) || ContentWorld::isPage(contentWorld)) { + if (completionHandler) { + completionHandler(-1); + } + return; + } + + auto& worldName = contentWorld->name; + if (!map_contains(contentWorlds_, worldName)) { + nlohmann::json parameters = { + {"frameId", webView_->pageFrameId()}, + {"worldName", worldName} + }; + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.createIsolatedWorld", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler, worldName](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode) && completionHandler) { + auto id = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson))["executionContextId"].get(); + addPluginScriptsIfRequired(std::make_shared(worldName)); + completionHandler(id); + } + return S_OK; + } + ).Get()); + if (failedAndLog(hr) && completionHandler) { + completionHandler(-1); + } + } + else if (completionHandler) { + completionHandler(contentWorlds_.at(worldName)); + } + } + + void UserContentController::addScriptToWebView(std::shared_ptr userScript, const std::function completionHandler) const + { + if (!webView_ || !(webView_->webView)) { + if (completionHandler) { + completionHandler(userScript->id); + } + } + + std::string source = userScript->source; + if (userScript->injectionTime == UserScriptInjectionTime::atDocumentEnd) { + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; + } + source = UserContentController::wrapSourceCodeAddChecks(source, userScript); + + nlohmann::json parameters = { + {"source", source} + }; + + if (userScript->contentWorld && !ContentWorld::isPage(userScript->contentWorld)) { + parameters["worldName"] = userScript->contentWorld->name; + } + + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.addScriptToEvaluateOnNewDocument", utf8_to_wide(parameters.dump()).c_str(), Callback( + [userScript, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + userScript->id = json["identifier"].get(); + } + if (completionHandler) { + completionHandler(userScript->id); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(userScript->id); + } + } + + void UserContentController::removeScriptFromWebView(std::shared_ptr userScript, const std::function completionHandler) const + { + if (!webView_ || !(webView_->webView)) { + if (completionHandler) { + completionHandler(); + } + return; + } + + nlohmann::json parameters = { + {"identifier", userScript->id} + }; + + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.removeScriptToEvaluateOnNewDocument", utf8_to_wide(parameters.dump()).c_str(), Callback( + [userScript, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + if (completionHandler) { + completionHandler(); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(); + } + } + + void UserContentController::addPluginScriptsIfRequired(const std::shared_ptr contentWorld) + { + if (contentWorld && !ContentWorld::isPage(contentWorld)) { + std::vector> pluginScriptsRequiredInAllContentWorlds = getPluginScriptsRequiredInAllContentWorlds(); + for (auto& pluginScript : pluginScriptsRequiredInAllContentWorlds) { + if (!containsPluginScript(pluginScript, contentWorld)) { + if (!map_contains(pluginScriptsInContentWorlds_, contentWorld->name)) { + pluginScriptsInContentWorlds_.insert({ contentWorld->name, {} }); + } + pluginScriptsInContentWorlds_.at(contentWorld->name).push_back(pluginScript); + addPluginScript(pluginScript->copyAndSet(contentWorld)); + } + } + } + } + + std::string UserContentController::wrapSourceCodeAddChecks(const std::string& source, const std::shared_ptr userScript) + { + auto allowedOriginRules = userScript->allowedOriginRules; + auto forMainFrameOnly = userScript->forMainFrameOnly; + + std::string ifStatement = "if ("; + + if (allowedOriginRules.has_value() && !vector_contains(allowedOriginRules.value(), "*")) { + if (allowedOriginRules.value().empty()) { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return ""; + } + + std::string jsRegExpArray = "["; + for (const auto& allowedOriginRule : allowedOriginRules.value()) { + if (jsRegExpArray.length() > 1) { + jsRegExpArray += ", "; + } + jsRegExpArray += "new RegExp('" + replace_all_copy(allowedOriginRule, "\'", "\\'") + "')"; + } + + if (jsRegExpArray.length() > 1) { + jsRegExpArray += "]"; + ifStatement += jsRegExpArray + ".some(function(rx) { return rx.test(window.location.origin); })"; + } + } + + if (forMainFrameOnly) { + if (ifStatement.length() > 4) { + ifStatement += " && "; + } + ifStatement += "window === window.top"; + } + + return ifStatement.length() > 4 ? ifStatement + ") { " + source + "}" : source; + } + + UserContentController::~UserContentController() + { + debugLog("dealloc UserContentController"); + removeAllUserOnlyScripts(); + removeAllPluginScripts(); + contentWorlds_.clear(); + pluginScriptsInContentWorlds_.clear(); + webView_ = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h new file mode 100644 index 000000000..c5045c3ed --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h @@ -0,0 +1,75 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ + +#include +#include +#include + +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../plugin_scripts_js/plugin_scripts_util.h" +#include "../types/content_world.h" +#include "../types/plugin_script.h" +#include "../types/user_script.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebView; + + class UserContentController + { + public: + UserContentController(InAppWebView* webView); + ~UserContentController(); + + std::vector> getUserOnlyScriptsAt(const UserScriptInjectionTime& injectionTime) const; + void addUserOnlyScript(std::shared_ptr userScript); + void addUserOnlyScripts(std::vector> userScripts); + void removeUserOnlyScript(std::shared_ptr userScript); + void removeUserOnlyScriptAt(const int64_t& index, const UserScriptInjectionTime& injectionTime); + void removeAllUserOnlyScripts(); + bool containsUserOnlyScript(std::shared_ptr userScript) const; + bool containsUserOnlyScriptByGroupName(const std::string& groupName) const; + void removeUserOnlyScriptsByGroupName(const std::string& groupName); + + std::vector> getPluginScriptsAt(const UserScriptInjectionTime& injectionTime) const; + void addPluginScript(std::shared_ptr pluginScript); + void addPluginScripts(std::vector> pluginScripts); + void removePluginScript(std::shared_ptr pluginScript); + void removeAllPluginScripts(); + bool containsPluginScript(std::shared_ptr pluginScript) const; + bool containsPluginScript(std::shared_ptr pluginScript, const std::shared_ptr contentWorld) const; + bool containsPluginScriptByGroupName(const std::string& groupName) const; + void removePluginScriptsByGroupName(const std::string& groupName); + std::vector> getPluginScriptsRequiredInAllContentWorlds() const; + + void registerEventHandlers(); + void createContentWorld(const std::shared_ptr contentWorld, const std::function completionHandler); + + private: + InAppWebView* webView_; + + // used to track Content World names -> Execution Context ID + std::map contentWorlds_; + // used only to track plugin script to inject inside new Content Worlds + std::map>> pluginScriptsInContentWorlds_; + + std::map>> pluginScripts_ = { + {UserScriptInjectionTime::atDocumentStart, {}}, + {UserScriptInjectionTime::atDocumentEnd, {}} + }; + + std::map>> userOnlyScripts_ = { + {UserScriptInjectionTime::atDocumentStart, {}}, + {UserScriptInjectionTime::atDocumentEnd, {}} + }; + + void addScriptToWebView(std::shared_ptr userScript, const std::function completionHandler) const; + void removeScriptFromWebView(std::shared_ptr userScript, const std::function completionHandler) const; + + void addPluginScriptsIfRequired(const std::shared_ptr contentWorld); + + static std::string wrapSourceCodeAddChecks(const std::string& source, const std::shared_ptr userScript); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp new file mode 100644 index 000000000..c09a23689 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp @@ -0,0 +1,650 @@ +#include "../in_app_browser/in_app_browser.h" +#include "../types/base_callback_result.h" +#include "../types/content_world.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "in_app_webview.h" +#include "webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + WebViewChannelDelegate::WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger) + : webView(webView), ChannelDelegate(messenger, InAppWebView::METHOD_CHANNEL_NAME_PREFIX + variant_to_string(webView->id)) + {} + + WebViewChannelDelegate::WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name) + : webView(webView), ChannelDelegate(messenger, name) + {} + + WebViewChannelDelegate::ShouldOverrideUrlLoadingCallback::ShouldOverrideUrlLoadingCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + if (!value || value->IsNull()) { + return NavigationActionPolicy::cancel; + } + auto navigationPolicy = std::get(*value); + return static_cast(navigationPolicy); + }; + } + + WebViewChannelDelegate::CallJsHandlerCallback::CallJsHandlerCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value; + }; + } + + WebViewChannelDelegate::CreateWindowCallback::CreateWindowCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + if (!value || value->IsNull()) { + return false; + } + auto handledByClient = std::get(*value); + return handledByClient; + }; + } + + WebViewChannelDelegate::PermissionRequestCallback::PermissionRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ShouldInterceptRequestCallback::ShouldInterceptRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::LoadResourceWithCustomSchemeCallback::LoadResourceWithCustomSchemeCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ReceivedHttpAuthRequestCallback::ReceivedHttpAuthRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ReceivedClientCertRequestCallback::ReceivedClientCertRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ReceivedServerTrustAuthRequestCallback::ReceivedServerTrustAuthRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::DownloadStartRequestCallback::DownloadStartRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + void WebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + if (!webView) { + result->Success(); + return; + } + + auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "getUrl")) { + result->Success(make_fl_value(webView->getUrl())); + } + else if (string_equals(methodName, "getTitle")) { + result->Success(make_fl_value(webView->getUrl())); + } + else if (string_equals(methodName, "loadUrl")) { + auto urlRequest = std::make_unique(get_fl_map_value(arguments, "urlRequest")); + webView->loadUrl(std::move(urlRequest)); + result->Success(true); + } + else if (string_equals(methodName, "loadFile")) { + auto assetFilePath = get_fl_map_value(arguments, "assetFilePath"); + webView->loadFile(assetFilePath); + result->Success(true); + } + else if (string_equals(methodName, "loadData")) { + auto data = get_fl_map_value(arguments, "data"); + webView->loadData(data); + result->Success(true); + } + else if (string_equals(methodName, "reload")) { + webView->reload(); + result->Success(true); + } + else if (string_equals(methodName, "goBack")) { + webView->goBack(); + result->Success(true); + } + else if (string_equals(methodName, "canGoBack")) { + result->Success(webView->canGoBack()); + } + else if (string_equals(methodName, "goForward")) { + webView->goForward(); + result->Success(true); + } + else if (string_equals(methodName, "canGoForward")) { + result->Success(webView->canGoForward()); + } + else if (string_equals(methodName, "goBackOrForward")) { + auto steps = get_fl_map_value(arguments, "steps"); + webView->goBackOrForward(steps); + result->Success(true); + } + else if (string_equals(methodName, "canGoBackOrForward")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto steps = get_fl_map_value(arguments, "steps"); + webView->canGoBackOrForward(steps, [result_ = std::move(result_)](const bool& value) + { + result_->Success(value); + }); + } + else if (string_equals(methodName, "isLoading")) { + result->Success(webView->isLoading()); + } + else if (string_equals(methodName, "stopLoading")) { + webView->stopLoading(); + result->Success(true); + } + else if (string_equals(methodName, "evaluateJavascript")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto source = get_fl_map_value(arguments, "source"); + auto contentWorldMap = get_optional_fl_map_value(arguments, "contentWorld"); + std::shared_ptr contentWorld = contentWorldMap.has_value() ? std::make_shared(contentWorldMap.value()) : ContentWorld::page(); + webView->evaluateJavascript(source, std::move(contentWorld), [result_ = std::move(result_)](const std::string& value) + { + result_->Success(value); + }); + } + else if (string_equals(methodName, "callAsyncJavaScript")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto functionBody = get_fl_map_value(arguments, "functionBody"); + auto argumentsAsJson = get_fl_map_value(arguments, "arguments"); + auto contentWorldMap = get_optional_fl_map_value(arguments, "contentWorld"); + std::shared_ptr contentWorld = contentWorldMap.has_value() ? std::make_shared(contentWorldMap.value()) : ContentWorld::page(); + webView->callAsyncJavaScript(functionBody, argumentsAsJson, std::move(contentWorld), [result_ = std::move(result_)](const std::string& value) + { + result_->Success(value); + }); + } + else if (string_equals(methodName, "getCopyBackForwardList")) { + auto result_ = std::shared_ptr>(std::move(result)); + webView->getCopyBackForwardList([result_ = std::move(result_)](const std::unique_ptr value) + { + result_->Success(value->toEncodableMap()); + }); + } + else if (string_equals(methodName, "addUserScript")) { + auto userScript = std::make_unique(get_fl_map_value(arguments, "userScript")); + webView->addUserScript(std::move(userScript)); + result->Success(true); + } + else if (string_equals(methodName, "removeUserScript")) { + auto index = get_fl_map_value(arguments, "index"); + auto userScript = std::make_unique(get_fl_map_value(arguments, "userScript")); + webView->removeUserScript(index, std::move(userScript)); + result->Success(true); + } + else if (string_equals(methodName, "removeUserScriptsByGroupName")) { + auto groupName = get_fl_map_value(arguments, "groupName"); + webView->removeUserScriptsByGroupName(groupName); + result->Success(true); + } + else if (string_equals(methodName, "removeAllUserScripts")) { + webView->removeAllUserScripts(); + result->Success(true); + } + else if (string_equals(methodName, "takeScreenshot")) { + auto result_ = std::shared_ptr>(std::move(result)); + auto screenshotConfigurationMap = get_optional_fl_map_value(arguments, "screenshotConfiguration"); + std::optional> screenshotConfiguration = + screenshotConfigurationMap.has_value() ? std::make_unique(screenshotConfigurationMap.value()) : std::optional>{}; + webView->takeScreenshot(std::move(screenshotConfiguration), [result_ = std::move(result_)](const std::optional data) + { + result_->Success(make_fl_value(data)); + }); + } + else if (string_equals(methodName, "setSettings")) { + if (webView->inAppBrowser) { + auto settingsMap = get_fl_map_value(arguments, "settings"); + auto settings = std::make_unique(settingsMap); + webView->inAppBrowser->setSettings(std::move(settings), settingsMap); + } + else { + auto settingsMap = get_fl_map_value(arguments, "settings"); + auto settings = std::make_unique(settingsMap); + webView->setSettings(std::move(settings), settingsMap); + } + result->Success(true); + } + else if (string_equals(methodName, "getSettings")) { + if (webView->inAppBrowser) { + result->Success(webView->inAppBrowser->getSettings()); + } + else { + result->Success(webView->getSettings()); + } + } + else if (string_equals(methodName, "openDevTools")) { + webView->openDevTools(); + result->Success(true); + } + else if (string_equals(methodName, "callDevToolsProtocolMethod")) { + auto result_ = std::shared_ptr>(std::move(result)); + auto cdpMethodName = get_fl_map_value(arguments, "methodName"); + auto parametersAsJson = get_optional_fl_map_value(arguments, "parametersAsJson"); + webView->callDevToolsProtocolMethod(cdpMethodName, parametersAsJson, [result_ = std::move(result_)](const HRESULT& errorCode, const std::optional& data) + { + if (SUCCEEDED(errorCode)) { + result_->Success(make_fl_value(data)); + } + else { + result_->Error(std::to_string(errorCode), getHRMessage(errorCode)); + } + }); + } + else if (string_equals(methodName, "addDevToolsProtocolEventListener")) { + auto eventName = get_fl_map_value(arguments, "eventName"); + webView->addDevToolsProtocolEventListener(eventName); + result->Success(true); + } + else if (string_equals(methodName, "removeDevToolsProtocolEventListener")) { + auto eventName = get_fl_map_value(arguments, "eventName"); + webView->removeDevToolsProtocolEventListener(eventName); + result->Success(true); + } + else if (string_equals(methodName, "pause")) { + webView->pause(); + result->Success(true); + } + else if (string_equals(methodName, "resume")) { + webView->resume(); + result->Success(true); + } + else if (string_equals(methodName, "getCertificate")) { + auto result_ = std::shared_ptr>(std::move(result)); + webView->getCertificate([result_ = std::move(result_)](const std::optional> data) + { + result_->Success(data.has_value() ? data.value()->toEncodableMap() : make_fl_value()); + }); + } + else if (string_equals(methodName, "clearSslPreferences")) { + auto result_ = std::shared_ptr>(std::move(result)); + webView->clearSslPreferences([result_ = std::move(result_)]() + { + result_->Success(); + }); + } + else if (string_equals(methodName, "isInterfaceSupported")) { + auto interfaceName = get_fl_map_value(arguments, "interface"); + result->Success(webView->isInterfaceSupported(interfaceName)); + } + else if (string_equals(methodName, "getZoomScale")) { + result->Success(webView->getZoomScale()); + } + // for inAppBrowser + else if (webView->inAppBrowser && string_equals(methodName, "show")) { + webView->inAppBrowser->show(); + result->Success(true); + } + else if (webView->inAppBrowser && string_equals(methodName, "hide")) { + webView->inAppBrowser->hide(); + result->Success(true); + } + else if (webView->inAppBrowser && string_equals(methodName, "close")) { + webView->inAppBrowser->close(); + result->Success(true); + } + else { + result->NotImplemented(); + } + } + + void WebViewChannelDelegate::onLoadStart(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onLoadStart", std::move(arguments)); + } + + void WebViewChannelDelegate::onLoadStop(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onLoadStop", std::move(arguments)); + } + + void WebViewChannelDelegate::shouldOverrideUrlLoading(std::shared_ptr navigationAction, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(navigationAction->toEncodableMap()); + channel->InvokeMethod("shouldOverrideUrlLoading", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedError(std::shared_ptr request, std::shared_ptr error) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"error", error->toEncodableMap()}, + }); + channel->InvokeMethod("onReceivedError", std::move(arguments)); + } + + void WebViewChannelDelegate::onReceivedHttpError(std::shared_ptr request, std::shared_ptr errorResponse) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"errorResponse", errorResponse->toEncodableMap()}, + }); + channel->InvokeMethod("onReceivedHttpError", std::move(arguments)); + } + + void WebViewChannelDelegate::onTitleChanged(const std::optional& title) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"title", make_fl_value(title)} + }); + channel->InvokeMethod("onTitleChanged", std::move(arguments)); + + if (webView && webView->inAppBrowser) { + webView->inAppBrowser->didChangeTitle(title); + } + } + + void WebViewChannelDelegate::onUpdateVisitedHistory(const std::optional& url, const std::optional& isReload) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"isReload", make_fl_value(isReload)} + }); + channel->InvokeMethod("onUpdateVisitedHistory", std::move(arguments)); + } + + void WebViewChannelDelegate::onCallJsHandler(const std::string& handlerName, const std::unique_ptr data, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"handlerName", handlerName}, + {"data", data->toEncodableMap()} + }); + channel->InvokeMethod("onCallJsHandler", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onConsoleMessage(const std::string& message, const int64_t& messageLevel) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"message", message}, + {"messageLevel", messageLevel} + }); + channel->InvokeMethod("onConsoleMessage", std::move(arguments)); + } + + void WebViewChannelDelegate::onDevToolsProtocolEventReceived(const std::string& eventName, const std::string& data) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"eventName", eventName}, + {"data", data} + }); + channel->InvokeMethod("onDevToolsProtocolEventReceived", std::move(arguments)); + } + + void WebViewChannelDelegate::onProgressChanged(const int64_t& progress) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"progress", progress} + }); + channel->InvokeMethod("onProgressChanged", std::move(arguments)); + } + + void WebViewChannelDelegate::onCreateWindow(std::shared_ptr createWindowAction, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(createWindowAction->toEncodableMap()); + channel->InvokeMethod("onCreateWindow", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onCloseWindow() const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(); + channel->InvokeMethod("onCloseWindow", std::move(arguments)); + } + + void WebViewChannelDelegate::onPermissionRequest(const std::string& origin, const std::vector& resources, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"origin", origin}, + {"resources", make_fl_value(resources)} + }); + channel->InvokeMethod("onPermissionRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::shouldInterceptRequest(std::shared_ptr request, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(make_fl_value(request->toEncodableMap())); + channel->InvokeMethod("shouldInterceptRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onLoadResourceWithCustomScheme(std::shared_ptr request, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + }); + channel->InvokeMethod("onLoadResourceWithCustomScheme", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedHttpAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedHttpAuthRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedClientCertRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedClientCertRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedServerTrustAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedServerTrustAuthRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onRenderProcessGone(const std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onDevToolsProtocolEventReceived", std::move(arguments)); + } + + void WebViewChannelDelegate::onRenderProcessUnresponsive(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onRenderProcessUnresponsive", std::move(arguments)); + } + void WebViewChannelDelegate::onWebContentProcessDidTerminate() const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(); + channel->InvokeMethod("onWebContentProcessDidTerminate", std::move(arguments)); + } + + void WebViewChannelDelegate::onProcessFailed(const std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onProcessFailed", std::move(arguments)); + } + + void WebViewChannelDelegate::onDownloadStarting(std::shared_ptr request, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(request->toEncodableMap()); + channel->InvokeMethod("onDownloadStarting", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onAcceleratorKeyPressed(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onAcceleratorKeyPressed", std::move(arguments)); + } + + void WebViewChannelDelegate::onZoomScaleChanged(const double& oldScale, const double& newScale) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"oldScale", make_fl_value(oldScale)}, + {"newScale", make_fl_value(newScale)}, + }); + channel->InvokeMethod("onZoomScaleChanged", std::move(arguments)); + } + + WebViewChannelDelegate::~WebViewChannelDelegate() + { + debugLog("dealloc WebViewChannelDelegate"); + webView = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h new file mode 100644 index 000000000..3421a9fbe --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h @@ -0,0 +1,136 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ + +#include + +#include "../types/accelerator_key_pressed_detail.h" +#include "../types/base_callback_result.h" +#include "../types/channel_delegate.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/download_start_request.h" +#include "../types/download_start_response.h" +#include "../types/http_auth_response.h" +#include "../types/http_authentication_challenge.h" +#include "../types/javascript_handler_function_data.h" +#include "../types/navigation_action.h" +#include "../types/permission_response.h" +#include "../types/process_failed_detail.h" +#include "../types/render_process_gone_detail.h" +#include "../types/server_trust_auth_response.h" +#include "../types/server_trust_challenge.h" +#include "../types/web_resource_error.h" +#include "../types/web_resource_request.h" +#include "../types/web_resource_response.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebView; + + enum class NavigationActionPolicy { cancel = 0, allow = 1 }; + + class WebViewChannelDelegate : public ChannelDelegate + { + public: + InAppWebView* webView; + + class ShouldOverrideUrlLoadingCallback : public BaseCallbackResult { + public: + ShouldOverrideUrlLoadingCallback(); + ~ShouldOverrideUrlLoadingCallback() = default; + }; + + class CallJsHandlerCallback : public BaseCallbackResult { + public: + CallJsHandlerCallback(); + ~CallJsHandlerCallback() = default; + }; + + class CreateWindowCallback : public BaseCallbackResult { + public: + CreateWindowCallback(); + ~CreateWindowCallback() = default; + }; + + class PermissionRequestCallback : public BaseCallbackResult> { + public: + PermissionRequestCallback(); + ~PermissionRequestCallback() = default; + }; + + class ShouldInterceptRequestCallback : public BaseCallbackResult> { + public: + ShouldInterceptRequestCallback(); + ~ShouldInterceptRequestCallback() = default; + }; + + class LoadResourceWithCustomSchemeCallback : public BaseCallbackResult> { + public: + LoadResourceWithCustomSchemeCallback(); + ~LoadResourceWithCustomSchemeCallback() = default; + }; + + class ReceivedHttpAuthRequestCallback : public BaseCallbackResult> { + public: + ReceivedHttpAuthRequestCallback(); + ~ReceivedHttpAuthRequestCallback() = default; + }; + + class ReceivedClientCertRequestCallback : public BaseCallbackResult> { + public: + ReceivedClientCertRequestCallback(); + ~ReceivedClientCertRequestCallback() = default; + }; + + class ReceivedServerTrustAuthRequestCallback : public BaseCallbackResult> { + public: + ReceivedServerTrustAuthRequestCallback(); + ~ReceivedServerTrustAuthRequestCallback() = default; + }; + + class DownloadStartRequestCallback : public BaseCallbackResult> { + public: + DownloadStartRequestCallback(); + ~DownloadStartRequestCallback() = default; + }; + + WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger); + WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name); + ~WebViewChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onLoadStart(const std::optional& url) const; + void onLoadStop(const std::optional& url) const; + void onProgressChanged(const int64_t& progress) const; + void shouldOverrideUrlLoading(std::shared_ptr navigationAction, std::unique_ptr callback) const; + void onReceivedError(std::shared_ptr request, std::shared_ptr error) const; + void onReceivedHttpError(std::shared_ptr request, std::shared_ptr error) const; + void onTitleChanged(const std::optional& title) const; + void onUpdateVisitedHistory(const std::optional& url, const std::optional& isReload) const; + void onCallJsHandler(const std::string& handlerName, const std::unique_ptr data, std::unique_ptr callback) const; + void onConsoleMessage(const std::string& message, const int64_t& messageLevel) const; + void onDevToolsProtocolEventReceived(const std::string& eventName, const std::string& data) const; + void onCreateWindow(std::shared_ptr createWindowAction, std::unique_ptr callback) const; + void onCloseWindow() const; + void onPermissionRequest(const std::string& origin, const std::vector& resources, std::unique_ptr callback) const; + void shouldInterceptRequest(std::shared_ptr request, std::unique_ptr callback) const; + void onLoadResourceWithCustomScheme(std::shared_ptr request, std::unique_ptr callback) const; + void onReceivedHttpAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onReceivedClientCertRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onReceivedServerTrustAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onRenderProcessGone(const std::shared_ptr detail) const; + void onRenderProcessUnresponsive(const std::optional& url) const; + void onWebContentProcessDidTerminate() const; + void onProcessFailed(const std::shared_ptr detail) const; + void onDownloadStarting(std::shared_ptr request, std::unique_ptr callback) const; + void onAcceleratorKeyPressed(std::shared_ptr detail) const; + void onZoomScaleChanged(const double& oldScale, const double& newScale) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h b/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h new file mode 100644 index 000000000..6a318ec03 --- /dev/null +++ b/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + FLUTTER_PLUGIN_EXPORT void FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ diff --git a/flutter_inappwebview_windows/windows/platform_util.cpp b/flutter_inappwebview_windows/windows/platform_util.cpp new file mode 100644 index 000000000..4de817672 --- /dev/null +++ b/flutter_inappwebview_windows/windows/platform_util.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +#include "in_app_webview/in_app_webview_manager.h" +#include "platform_util.h" +#include "types/callbacks_complete.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + PlatformUtil::PlatformUtil(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), PlatformUtil::METHOD_CHANNEL_NAME_PREFIX) + {} + + void PlatformUtil::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + result->NotImplemented(); + } + + void PlatformUtil::_EmitEvent(std::string eventName) + { + if (channel == nullptr) + return; + flutter::EncodableMap args = flutter::EncodableMap(); + args[flutter::EncodableValue("eventName")] = flutter::EncodableValue(eventName); + channel->InvokeMethod( + "onEvent", std::make_unique(args)); + } + + std::optional PlatformUtil::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) noexcept + { + std::optional result = std::nullopt; + + if (message == WM_MOVING) { + window_is_moving_ = true; + if (!window_start_move_sent_) { + window_start_move_sent_ = true; + _EmitEvent("onWindowStartMove"); + } + _EmitEvent("onWindowMove"); + } + else if (message == WM_EXITSIZEMOVE) { + if (window_is_moving_) { + window_is_moving_ = false; + window_start_move_sent_ = false; + _EmitEvent("onWindowEndMove"); + } + } + + return result; + } + + PlatformUtil::~PlatformUtil() + { + debugLog("dealloc PlatformUtil"); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/platform_util.h b/flutter_inappwebview_windows/windows/platform_util.h new file mode 100644 index 000000000..75b5d795b --- /dev/null +++ b/flutter_inappwebview_windows/windows/platform_util.h @@ -0,0 +1,41 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ + +#include +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + class PlatformUtil : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_platformutil"; + + const FlutterInappwebviewWindowsPlugin* plugin; + + PlatformUtil(const FlutterInappwebviewWindowsPlugin* plugin); + ~PlatformUtil(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + std::optional PlatformUtil::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) noexcept; + + private: + void PlatformUtil::_EmitEvent(std::string eventName); + bool window_is_moving_ = false; + bool window_start_move_sent_ = false; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h new file mode 100644 index 000000000..2c78a75e6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h @@ -0,0 +1,110 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ + +#include +#include + +#include "../types/plugin_script.h" +#include "../utils/log.h" +#include "../utils/string.h" + +namespace flutter_inappwebview_plugin +{ + + 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"; + + static std::string JAVASCRIPT_BRIDGE_JS_SOURCE() + { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + " = {}; \ + (function(window) {\ + var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';\ + var origin = '';\ + var requestUrl = '';\ + var isMainFrame = false;\ + var _JSON_stringify;\ + var _Array_slice;\ + var _setTimeout;\ + var _Promise;\ + var _postMessage;\ + try {\ + origin = window.location.origin;\ + } catch (_) {}\ + try {\ + requestUrl = window.location.href;\ + } catch (_) {}\ + try {\ + isMainFrame = window === window.top;\ + } catch (_) {}\ + try {\ + _JSON_stringify = window.JSON.stringify;\ + _Array_slice = window.Array.prototype.slice;\ + _Array_slice.call = window.Function.prototype.call;\ + _setTimeout = window.setTimeout;\ + _Promise = window.Promise;\ + _postMessage = window.chrome.webview.postMessage;\ + } catch (_) { return; }\ + window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() { \ + var _callHandlerID = _setTimeout(function() {}); \ + _postMessage({ 'name': 'callHandler', 'body': {\ + 'handlerName': arguments[0],\ + '_callHandlerID' : _callHandlerID,\ + '_bridgeSecret': bridgeSecret,\ + 'origin': origin,\ + 'requestUrl': requestUrl,\ + 'args' : _JSON_stringify(_Array_slice.call(arguments, 1))}\ + });\ + return new _Promise(function(resolve, reject) { \ + try {\ + (isMainFrame ? window : window.top)." + get_JAVASCRIPT_BRIDGE_NAME() + "[_callHandlerID] = { resolve: resolve, reject : reject };\ + } catch(e) { resolve(); }\ + });\ + };\ + })(window);"; + } + + static std::string PLATFORM_READY_JS_SOURCE() + { + return "(function() { \ + if ((window.top == null || window.top === window) && window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null && window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady == null) { \ + window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady')); \ + window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady = true; \ + } \ + })();"; + } + + static std::unique_ptr JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(const std::string& expectedBridgeSecret, + const std::optional>& allowedOriginRules, const bool forMainFrameOnly) + { + auto source = replace_all_copy(JAVASCRIPT_BRIDGE_JS_SOURCE(), VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, expectedBridgeSecret); + return std::make_unique( + JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source, + UserScriptInjectionTime::atDocumentStart, + forMainFrameOnly, + allowedOriginRules, + nullptr, + true + ); + } + + private: + inline static std::string _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h new file mode 100644 index 000000000..c5990505f --- /dev/null +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h @@ -0,0 +1,14 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + const std::string VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"; + const std::string VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES"; + const std::string VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES"; + const std::string VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY"; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp b/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp new file mode 100644 index 000000000..f2504fd60 --- /dev/null +++ b/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" + +namespace flutter_inappwebview_windows { +namespace test { + +namespace { + +using flutter::EncodableMap; +using flutter::EncodableValue; +using flutter::MethodCall; +using flutter::MethodResultFunctions; + +} // namespace + +TEST(FlutterInappwebviewWindowsPlugin, GetPlatformVersion) { + FlutterInappwebviewWindowsPlugin plugin; + // Save the reply value from the success callback. + std::string result_string; + plugin.HandleMethodCall( + MethodCall("getPlatformVersion", std::make_unique()), + std::make_unique>( + [&result_string](const EncodableValue* result) { + result_string = std::get(*result); + }, + nullptr, nullptr)); + + // Since the exact string varies by host, just ensure that it's a string + // with the expected format. + EXPECT_TRUE(result_string.rfind("Windows ", 0) == 0); +} + +} // namespace test +} // namespace flutter_inappwebview_windows diff --git a/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp new file mode 100644 index 000000000..dcb8da55c --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp @@ -0,0 +1,34 @@ +#include "../utils/flutter.h" +#include "accelerator_key_pressed_detail.h" + +namespace flutter_inappwebview_plugin +{ + AcceleratorKeyPressedDetail::AcceleratorKeyPressedDetail(const std::optional& keyEventKind, + const std::optional> physicalKeyStatus, + const std::optional& virtualKey) + : keyEventKind(keyEventKind), physicalKeyStatus(physicalKeyStatus), virtualKey(virtualKey) + {} + + std::unique_ptr AcceleratorKeyPressedDetail::fromICoreWebView2AcceleratorKeyPressedEventArgs(const wil::com_ptr args) + { + COREWEBVIEW2_KEY_EVENT_KIND kind; + std::optional keyEventKind = SUCCEEDED(args->get_KeyEventKind(&kind)) ? (int64_t)kind : std::optional{}; + + COREWEBVIEW2_PHYSICAL_KEY_STATUS status; + std::optional> physicalKeyStatus = SUCCEEDED(args->get_PhysicalKeyStatus(&status)) ? PhysicalKeyStatus::fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(status) : std::optional>{}; + + UINT vKey; + std::optional virtualKey = SUCCEEDED(args->get_VirtualKey(&vKey)) ? (int64_t)vKey : std::optional{}; + + return std::make_unique(keyEventKind, physicalKeyStatus, virtualKey); + } + + flutter::EncodableMap AcceleratorKeyPressedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + { "keyEventKind", make_fl_value(keyEventKind) }, + { "physicalKeyStatus", physicalKeyStatus.has_value() ? make_fl_value(physicalKeyStatus.value()->toEncodableMap()) : make_fl_value() }, + { "virtualKey", make_fl_value(virtualKey) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h new file mode 100644 index 000000000..acf33a28f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H +#define FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H + +#include +#include +#include +#include + +#include "physical_key_status.h" + +namespace flutter_inappwebview_plugin +{ + class AcceleratorKeyPressedDetail + { + public: + const std::optional keyEventKind; + const std::optional> physicalKeyStatus; + const std::optional virtualKey; + + AcceleratorKeyPressedDetail(const std::optional& keyEventKind, + const std::optional> physicalKeyStatus, + const std::optional& virtualKey); + ~AcceleratorKeyPressedDetail() = default; + + static std::unique_ptr fromICoreWebView2AcceleratorKeyPressedEventArgs(const wil::com_ptr args); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/base_callback_result.h b/flutter_inappwebview_windows/windows/types/base_callback_result.h new file mode 100644 index 000000000..d156e1e69 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/base_callback_result.h @@ -0,0 +1,57 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + class BaseCallbackResult : public flutter::MethodResultFunctions + { + public: + flutter::ResultHandlerError error; + flutter::ResultHandlerNotImplemented notImplemented; + std::function nonNullSuccess = [](const T result) { return true; }; + std::function nullSuccess = []() { return true; }; + std::function result)> defaultBehaviour = [](const std::optional result) {}; + std::function(const flutter::EncodableValue* result)> decodeResult = [](const flutter::EncodableValue* result) { return std::nullopt; }; + + BaseCallbackResult() : + MethodResultFunctions( + [this](const flutter::EncodableValue* val) + { + std::optional result = decodeResult ? decodeResult(val) : std::nullopt; + auto shouldRunDefaultBehaviour = false; + if (result.has_value()) { + shouldRunDefaultBehaviour = nonNullSuccess ? nonNullSuccess(result.value()) : shouldRunDefaultBehaviour; + } + else { + shouldRunDefaultBehaviour = nullSuccess ? nullSuccess() : shouldRunDefaultBehaviour; + } + if (shouldRunDefaultBehaviour && defaultBehaviour) { + defaultBehaviour(result); + } + }, + [this](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + if (error) { + error(error_code, error_message, error_details); + } + }, + [this]() + { + if (defaultBehaviour) { + defaultBehaviour(std::nullopt); + } + if (notImplemented) { + notImplemented(); + } + }) + {}; + virtual ~BaseCallbackResult() {}; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp new file mode 100644 index 000000000..f92c58ea5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp @@ -0,0 +1,17 @@ +#include "../utils/flutter.h" +#include "browser_process_exited_detail.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessExitedDetail::BrowserProcessExitedDetail(const std::optional& kind, const std::optional& processId) + : kind(kind), processId(processId) + {} + + flutter::EncodableMap BrowserProcessExitedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"processId", make_fl_value(processId)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h new file mode 100644 index 000000000..158a92785 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessExitedDetail + { + public: + const std::optional kind; + const std::optional processId; + + BrowserProcessExitedDetail(const std::optional& kind, + const std::optional& processId); + ~BrowserProcessExitedDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_info.cpp b/flutter_inappwebview_windows/windows/types/browser_process_info.cpp new file mode 100644 index 000000000..dd78fe414 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_info.cpp @@ -0,0 +1,62 @@ +#include "../utils/flutter.h" +#include "../utils/vector.h" +#include "browser_process_info.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessInfo::BrowserProcessInfo(const std::optional& kind, const std::optional& processId, const std::optional>>& frameInfos) + : kind(kind), processId(processId), frameInfos(frameInfos) + {} + + std::unique_ptr BrowserProcessInfo::fromICoreWebView2ProcessInfo(const wil::com_ptr processInfo) + { + COREWEBVIEW2_PROCESS_KIND processKind; + std::optional kind = SUCCEEDED(processInfo->get_Kind(&processKind)) ? static_cast(processKind) : std::optional{}; + + INT32 pid; + std::optional processId = SUCCEEDED(processInfo->get_ProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + const std::optional>> frameInfos = std::optional>>{}; + + return std::make_unique(kind, processId, frameInfos); + } + + std::unique_ptr BrowserProcessInfo::fromICoreWebView2ProcessExtendedInfo(const wil::com_ptr processExtendedInfo) + { + wil::com_ptr processInfo; + processExtendedInfo->get_ProcessInfo(&processInfo); + + COREWEBVIEW2_PROCESS_KIND processKind; + std::optional kind = processInfo && SUCCEEDED(processInfo->get_Kind(&processKind)) ? static_cast(processKind) : std::optional{}; + + INT32 pid; + std::optional processId = processInfo && SUCCEEDED(processInfo->get_ProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + std::optional>> frameInfos = std::optional>>{}; + wil::com_ptr frameInfoCollection; + if (SUCCEEDED(processExtendedInfo->get_AssociatedFrameInfos(&frameInfoCollection))) { + wil::com_ptr iterator; + if (SUCCEEDED(frameInfoCollection->GetIterator(&iterator))) { + frameInfos = std::vector>{}; + BOOL hasCurrent = FALSE; + if (SUCCEEDED(iterator->get_HasCurrent(&hasCurrent)) && hasCurrent) { + wil::com_ptr frameInfo; + if (SUCCEEDED(iterator->GetCurrent(&frameInfo))) { + frameInfos.value().push_back(FrameInfo::fromICoreWebView2FrameInfo(frameInfo)); + } + } + } + } + + return std::make_unique(kind, processId, frameInfos); + } + + flutter::EncodableMap BrowserProcessInfo::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"processId", make_fl_value(processId)}, + {"frameInfos", frameInfos.has_value() ? make_fl_value(functional_map(frameInfos.value(), [](const std::shared_ptr& frameInfo) { return frameInfo->toEncodableMap(); })) : make_fl_value()} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_info.h b/flutter_inappwebview_windows/windows/types/browser_process_info.h new file mode 100644 index 000000000..56e20e31f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_info.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ + +#include +#include +#include +#include + +#include "../types/frame_info.h" + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessInfo + { + public: + const std::optional kind; + const std::optional processId; + const std::optional>> frameInfos; + + BrowserProcessInfo(const std::optional& kind, + const std::optional& processId, + const std::optional>>& frameInfos); + ~BrowserProcessInfo() = default; + + static std::unique_ptr fromICoreWebView2ProcessInfo(const wil::com_ptr processInfo); + static std::unique_ptr fromICoreWebView2ProcessExtendedInfo(const wil::com_ptr processExtendedInfo); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp new file mode 100644 index 000000000..e6e3a9210 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp @@ -0,0 +1,47 @@ +#include "../utils/flutter.h" +#include "../utils/vector.h" +#include "browser_process_infos_changed_detail.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessInfosChangedDetail::BrowserProcessInfosChangedDetail(const std::vector>& infos) + : infos(infos) + {} + + std::unique_ptr BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(const wil::com_ptr processCollection) + { + std::vector> infos = {}; + UINT count; + if (SUCCEEDED(processCollection->get_Count(&count))) { + for (UINT i = 0; i < count; i++) { + wil::com_ptr processInfo; + if (SUCCEEDED(processCollection->GetValueAtIndex(i, &processInfo))) { + infos.push_back(BrowserProcessInfo::fromICoreWebView2ProcessInfo(processInfo)); + } + } + } + return std::make_unique(infos); + } + + std::unique_ptr BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(const wil::com_ptr processCollection) + { + std::vector> infos = {}; + UINT count; + if (SUCCEEDED(processCollection->get_Count(&count))) { + for (UINT i = 0; i < count; i++) { + wil::com_ptr processInfo; + if (SUCCEEDED(processCollection->GetValueAtIndex(i, &processInfo))) { + infos.push_back(BrowserProcessInfo::fromICoreWebView2ProcessExtendedInfo(processInfo)); + } + } + } + return std::make_unique(infos); + } + + flutter::EncodableMap BrowserProcessInfosChangedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + { "infos", make_fl_value(functional_map(infos, [](const std::shared_ptr& info) { return info->toEncodableMap(); })) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h new file mode 100644 index 000000000..225a552b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ + +#include + +#include "browser_process_info.h" + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessInfosChangedDetail + { + public: + const std::vector> infos; + + BrowserProcessInfosChangedDetail(const std::vector>& infos); + ~BrowserProcessInfosChangedDetail() = default; + + static std::unique_ptr fromICoreWebView2ProcessInfoCollection(const wil::com_ptr processCollection); + static std::unique_ptr fromICoreWebView2ProcessExtendedInfoCollection(const wil::com_ptr processCollection); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/callbacks_complete.h b/flutter_inappwebview_windows/windows/types/callbacks_complete.h new file mode 100644 index 000000000..c2ba57041 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/callbacks_complete.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + class CallbacksComplete + { + public: + std::function&)> onComplete; + + CallbacksComplete(const std::function&)> onComplete) + : onComplete(onComplete) + {} + + ~CallbacksComplete() + { + if (onComplete) { + onComplete(values_); + } + } + + void addValue(const T& value) + { + const std::lock_guard lock(mutex_); + values_.push_back(value); + } + + private: + std::vector values_; + std::mutex mutex_; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.cpp b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp new file mode 100644 index 000000000..b5db15048 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include "../utils/util.h" +#include "channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + ChannelDelegate::ChannelDelegate(flutter::BinaryMessenger* messenger, const std::string& name) : messenger(messenger) + { + channel = std::make_shared>( + this->messenger, name, + &flutter::StandardMethodCodec::GetInstance() + ); + channel->SetMethodCallHandler( + [this](const auto& call, auto result) + { + this->HandleMethodCall(call, std::move(result)); + }); + } + + void ChannelDelegate::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) + {} + + void ChannelDelegate::UnregisterMethodCallHandler() const + { + if (channel) { + channel->SetMethodCallHandler(nullptr); + } + } + + ChannelDelegate::~ChannelDelegate() + { + messenger = nullptr; + channel.reset(); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.h b/flutter_inappwebview_windows/windows/types/channel_delegate.h new file mode 100644 index 000000000..5ccfdfa12 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + class ChannelDelegate + { + using FlutterMethodChannel = std::shared_ptr>; + + public: + FlutterMethodChannel channel; + flutter::BinaryMessenger* messenger; + + ChannelDelegate(flutter::BinaryMessenger* messenger, const std::string& name); + virtual ~ChannelDelegate(); + + virtual void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void UnregisterMethodCallHandler() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp b/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp new file mode 100644 index 000000000..f8359a8ee --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp @@ -0,0 +1,21 @@ +#include "../utils/flutter.h" +#include "client_cert_challenge.h" + +namespace flutter_inappwebview_plugin +{ + ClientCertChallenge::ClientCertChallenge(const std::shared_ptr protectionSpace, + const std::vector& allowedCertificateAuthorities, + const bool& isProxy, + const std::vector>& mutuallyTrustedCertificates) + : URLAuthenticationChallenge(protectionSpace), allowedCertificateAuthorities(allowedCertificateAuthorities), isProxy(isProxy), mutuallyTrustedCertificates(mutuallyTrustedCertificates) + {} + + flutter::EncodableMap ClientCertChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + map.insert({ "allowedCertificateAuthorities", make_fl_value(allowedCertificateAuthorities) }); + map.insert({ "isProxy", make_fl_value(isProxy) }); + map.insert({ "mutuallyTrustedCertificates", make_fl_value(functional_map(mutuallyTrustedCertificates, [](const std::shared_ptr& item) { return item->toEncodableMap(); })) }); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_challenge.h b/flutter_inappwebview_windows/windows/types/client_cert_challenge.h new file mode 100644 index 000000000..4f3a4c0ac --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_challenge.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ + +#include +#include +#include + +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + class ClientCertChallenge : URLAuthenticationChallenge + { + public: + const std::vector allowedCertificateAuthorities; + const bool isProxy; + const std::vector> mutuallyTrustedCertificates; + + ClientCertChallenge(const std::shared_ptr protectionSpace, + const std::vector& allowedCertificateAuthorities, + const bool& isProxy, + const std::vector>& mutuallyTrustedCertificates); + ~ClientCertChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_response.cpp b/flutter_inappwebview_windows/windows/types/client_cert_response.cpp new file mode 100644 index 000000000..ab33f5491 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_response.cpp @@ -0,0 +1,23 @@ +#include "../utils/flutter.h" +#include "client_cert_response.h" + +namespace flutter_inappwebview_plugin +{ + ClientCertResponse::ClientCertResponse(const int64_t& selectedCertificate, + const std::optional& action) + : selectedCertificate(selectedCertificate), action(action) + {} + + ClientCertResponse::ClientCertResponse(const flutter::EncodableMap& map) + : ClientCertResponse(get_fl_map_value(map, "selectedCertificate"), + ClientCertResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap ClientCertResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"selectedCertificate", make_fl_value(selectedCertificate)}, + {"action", make_fl_value(ClientCertResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_response.h b/flutter_inappwebview_windows/windows/types/client_cert_response.h new file mode 100644 index 000000000..bdd9586fd --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_response.h @@ -0,0 +1,63 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class ClientCertResponseAction { + cancel = 0, + proceed, + ignore + }; + + inline ClientCertResponseAction ClientCertResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return ClientCertResponseAction::cancel; + } + switch (action.value()) { + case 0: + return ClientCertResponseAction::cancel; + case 1: + return ClientCertResponseAction::proceed; + case 2: + return ClientCertResponseAction::ignore; + default: + return ClientCertResponseAction::cancel; + } + } + + inline std::optional ClientCertResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class ClientCertResponse + { + public: + const int64_t selectedCertificate; + const std::optional action; + + ClientCertResponse(const int64_t& selectedCertificate, + const std::optional& action); + ClientCertResponse(const flutter::EncodableMap& map); + ~ClientCertResponse() = default; + + bool ClientCertResponse::operator==(const ClientCertResponse& other) + { + return selectedCertificate == other.selectedCertificate && + action == other.action; + } + bool ClientCertResponse::operator!=(const ClientCertResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/content_world.cpp b/flutter_inappwebview_windows/windows/types/content_world.cpp new file mode 100644 index 000000000..1c9ec1fee --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/content_world.cpp @@ -0,0 +1,60 @@ +#include "content_world.h" + +namespace flutter_inappwebview_plugin +{ + namespace { + const std::shared_ptr ContentWorldPage = std::make_shared("page"); + const std::shared_ptr ContentWorldDefaultClient = std::make_shared("defaultClient"); + } + + ContentWorld::ContentWorld(const std::string& name) + : name(name) + {} + + ContentWorld::ContentWorld(const flutter::EncodableMap& map) + : ContentWorld(get_fl_map_value(map, "name")) + {} + + bool ContentWorld::isSame(const ContentWorld& contentWorld) const + { + return name == contentWorld.name; + } + + bool ContentWorld::isSame(const std::shared_ptr contentWorld) const + { + return contentWorld && name == contentWorld->name; + } + + const std::shared_ptr ContentWorld::page() + { + return ContentWorldPage; + } + + const std::shared_ptr ContentWorld::defaultClient() + { + return ContentWorldDefaultClient; + } + + bool ContentWorld::isPage(const ContentWorld& contentWorld) + { + return contentWorld.isSame(*ContentWorld::page()); + } + + bool ContentWorld::isPage(const std::shared_ptr contentWorld) + { + return ContentWorld::page()->isSame(contentWorld); + } + + bool ContentWorld::isDefaultClient(const ContentWorld& contentWorld) + { + return contentWorld.isSame(*ContentWorld::defaultClient()); + } + + bool ContentWorld::isDefaultClient(const std::shared_ptr contentWorld) + { + return ContentWorld::defaultClient()->isSame(contentWorld); + } + + ContentWorld::~ContentWorld() + {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/content_world.h b/flutter_inappwebview_windows/windows/types/content_world.h new file mode 100644 index 000000000..46622289d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/content_world.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class ContentWorld + { + public: + + const std::string name; + + ContentWorld(const std::string& name); + ContentWorld(const flutter::EncodableMap& map); + ~ContentWorld(); + + bool isSame(const ContentWorld& contentWorld) const; + bool isSame(const std::shared_ptr contentWorld) const; + + const static std::shared_ptr page(); + const static std::shared_ptr defaultClient(); + + static bool isPage(const ContentWorld& contentWorld); + static bool isPage(const std::shared_ptr contentWorld); + static bool isDefaultClient(const ContentWorld& contentWorld); + static bool isDefaultClient(const std::shared_ptr contentWorld); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/create_window_action.cpp b/flutter_inappwebview_windows/windows/types/create_window_action.cpp new file mode 100644 index 000000000..087b24ccc --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/create_window_action.cpp @@ -0,0 +1,22 @@ +#include "../utils/flutter.h" +#include "create_window_action.h" + +namespace flutter_inappwebview_plugin +{ + CreateWindowAction::CreateWindowAction(std::shared_ptr request, const int64_t& windowId, + const bool& isForMainFrame, const std::optional& hasGesture, const std::optional> windowFeatures) + : request(std::move(request)), windowId(windowId), isForMainFrame(isForMainFrame), + hasGesture(hasGesture), windowFeatures(std::move(windowFeatures)) + {} + + flutter::EncodableMap CreateWindowAction::toEncodableMap() const + { + return flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"isForMainFrame", isForMainFrame}, + {"windowId", windowId}, + {"hasGesture", make_fl_value(hasGesture)}, + {"windowFeatures", windowFeatures.has_value() ? windowFeatures.value()->toEncodableMap() : make_fl_value()} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/create_window_action.h b/flutter_inappwebview_windows/windows/types/create_window_action.h new file mode 100644 index 000000000..84c20cd59 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/create_window_action.h @@ -0,0 +1,28 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ + +#include +#include + +#include "url_request.h" +#include "window_features.h" + +namespace flutter_inappwebview_plugin +{ + class CreateWindowAction + { + public: + const std::shared_ptr request; + const int64_t windowId; + const bool isForMainFrame; + const std::optional hasGesture; + const std::optional> windowFeatures; + + CreateWindowAction(std::shared_ptr request, const int64_t& windowId, const bool& isForMainFrame, const std::optional& hasGesture, const std::optional> windowFeatures); + ~CreateWindowAction() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CREATE_WINDOW_ACTION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp new file mode 100644 index 000000000..1a25945b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp @@ -0,0 +1,55 @@ +#include "../utils/flutter.h" +#include "../utils/strconv.h" +#include "custom_scheme_registration.h" + +namespace flutter_inappwebview_plugin +{ + CustomSchemeRegistration::CustomSchemeRegistration(const std::string& scheme, const std::optional>& allowedOrigins, + const std::optional& treatAsSecure, const std::optional& hasAuthorityComponent) + : scheme(scheme), allowedOrigins(allowedOrigins), treatAsSecure(treatAsSecure), hasAuthorityComponent(hasAuthorityComponent) + {} + + CustomSchemeRegistration::CustomSchemeRegistration(const flutter::EncodableMap& map) + : CustomSchemeRegistration(get_fl_map_value(map, "scheme"), + get_optional_fl_map_value>(map, "allowedOrigins"), + get_optional_fl_map_value(map, "treatAsSecure"), + get_optional_fl_map_value(map, "hasAuthorityComponent")) + {} + + flutter::EncodableMap CustomSchemeRegistration::toEncodableMap() const + { + return flutter::EncodableMap{ + {"scheme", make_fl_value(scheme)}, + {"allowedOrigins", make_fl_value(allowedOrigins)}, + {"treatAsSecure", make_fl_value(treatAsSecure)}, + {"hasAuthorityComponent", make_fl_value(hasAuthorityComponent)} + }; + } + + CoreWebView2CustomSchemeRegistration* CustomSchemeRegistration::toWebView2CustomSchemeRegistration() const + { + auto customSchemeRegistration = Microsoft::WRL::Make(utf8_to_wide(scheme).c_str()); + + if (allowedOrigins.has_value()) { + std::vector wideAllowedOrigins; + for (const auto& origin : allowedOrigins.value()) { + wideAllowedOrigins.push_back(utf8_to_wide(origin).c_str()); + } + customSchemeRegistration->SetAllowedOrigins( + static_cast(wideAllowedOrigins.size()), + wideAllowedOrigins.data()); + } + + if (treatAsSecure.has_value()) { + customSchemeRegistration->put_TreatAsSecure(treatAsSecure.value()); + } + + if (hasAuthorityComponent.has_value()) { + customSchemeRegistration->put_HasAuthorityComponent(hasAuthorityComponent.value()); + } + + customSchemeRegistration->AddRef(); + return customSchemeRegistration.Get(); + } + +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_registration.h b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.h new file mode 100644 index 000000000..d3c89efd2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_REGISTRATION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_REGISTRATION_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class CustomSchemeRegistration + { + public: + const std::string scheme; + const std::optional> allowedOrigins; + const std::optional treatAsSecure; + const std::optional hasAuthorityComponent; + + CustomSchemeRegistration(const std::string& scheme, const std::optional>& allowedOrigins, const std::optional& treatAsSecure, const std::optional& hasAuthorityComponent); + CustomSchemeRegistration(const flutter::EncodableMap& map); + ~CustomSchemeRegistration() = default; + + flutter::EncodableMap toEncodableMap() const; + CoreWebView2CustomSchemeRegistration* toWebView2CustomSchemeRegistration() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_REGISTRATION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp b/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp new file mode 100644 index 000000000..0ef63dd56 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp @@ -0,0 +1,62 @@ +#include "../utils/flutter.h" +#include "../utils/string.h" +#include "custom_scheme_response.h" + +#include + +namespace flutter_inappwebview_plugin +{ + CustomSchemeResponse::CustomSchemeResponse(const std::vector& data, const std::string& contentType, const std::string& contentEncoding) + : data(data), contentType(contentType), contentEncoding(contentEncoding) + {} + + CustomSchemeResponse::CustomSchemeResponse(const flutter::EncodableMap& map) + : CustomSchemeResponse(get_fl_map_value>(map, "data"), + get_fl_map_value(map, "contentType"), + get_fl_map_value(map, "contentEncoding")) + {} + + flutter::EncodableMap CustomSchemeResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"data", make_fl_value(data)}, + {"contentType", make_fl_value(contentType)}, + {"contentEncoding", make_fl_value(contentEncoding)} + }; + } + + ICoreWebView2WebResourceResponse* CustomSchemeResponse::toWebView2Response(const wil::com_ptr webViewEnvironment) const + { + wil::com_ptr webResourceResponse; + + if (webViewEnvironment) { + wil::com_ptr postDataStream = nullptr; + if (!data.empty()) { + auto postData = std::string(data.begin(), data.end()); + postDataStream = SHCreateMemStream( + reinterpret_cast(postData.data()), static_cast(postData.length())); + } + + webViewEnvironment->CreateWebResourceResponse( + postDataStream.get(), + 200, // Default to 200 + L"OK", // Default to "OK" + nullptr, + &webResourceResponse); + + wil::com_ptr responseHeaders; + if (SUCCEEDED(webResourceResponse->get_Headers(&responseHeaders))) { + if (!contentType.empty()) { + responseHeaders->AppendHeader(L"Content-Type", utf8_to_wide(contentType).c_str()); + } + if (!contentEncoding.empty()) { + responseHeaders->AppendHeader(L"Content-Encoding", utf8_to_wide(contentEncoding).c_str()); + } + } + + webResourceResponse->AddRef(); + } + + return webResourceResponse.get(); + } +} diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_response.h b/flutter_inappwebview_windows/windows/types/custom_scheme_response.h new file mode 100644 index 000000000..6b98d9095 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_response.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class CustomSchemeResponse + { + public: + const std::vector data; + const std::string contentType; + const std::string contentEncoding; + + CustomSchemeResponse(const std::vector& data, const std::string& contentType, const std::string& contentEncoding); + CustomSchemeResponse(const flutter::EncodableMap& map); + ~CustomSchemeResponse() = default; + + flutter::EncodableMap toEncodableMap() const; + ICoreWebView2WebResourceResponse* toWebView2Response(const wil::com_ptr webViewEnvironment) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CUSTOM_SCHEME_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_request.cpp b/flutter_inappwebview_windows/windows/types/download_start_request.cpp new file mode 100644 index 000000000..b6da8377b --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_request.cpp @@ -0,0 +1,24 @@ +#include "../utils/flutter.h" +#include "download_start_request.h" + +namespace flutter_inappwebview_plugin +{ + DownloadStartRequest::DownloadStartRequest(const std::optional& contentDisposition, + const int64_t& contentLength, + const std::optional& mimeType, + const std::optional& suggestedFilename, + const std::string& url) + : contentDisposition(contentDisposition), contentLength(contentLength), mimeType(mimeType), suggestedFilename(suggestedFilename), url(url) + {} + + flutter::EncodableMap DownloadStartRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"contentDisposition", make_fl_value(contentDisposition)}, + {"contentLength", make_fl_value(contentLength)}, + {"mimeType", make_fl_value(mimeType)}, + {"suggestedFilename", make_fl_value(suggestedFilename)}, + {"url", make_fl_value(url)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_request.h b/flutter_inappwebview_windows/windows/types/download_start_request.h new file mode 100644 index 000000000..08844e50c --- /dev/null +++ b/flutter_inappwebview_windows/windows/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 + +namespace flutter_inappwebview_plugin +{ + class DownloadStartRequest + { + public: + const std::optional contentDisposition; + const int64_t contentLength; + const std::optional mimeType; + const std::optional suggestedFilename; + const std::string url; + + DownloadStartRequest(const std::optional& contentDisposition, + const int64_t& contentLength, + const std::optional& mimeType, + const std::optional& suggestedFilename, + const std::string& url); + ~DownloadStartRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_response.cpp b/flutter_inappwebview_windows/windows/types/download_start_response.cpp new file mode 100644 index 000000000..03cce81ec --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_response.cpp @@ -0,0 +1,26 @@ +#include "../utils/flutter.h" +#include "download_start_response.h" + +namespace flutter_inappwebview_plugin +{ + DownloadStartResponse::DownloadStartResponse(const bool& handled, + const std::optional& action, + const std::optional& resultFilePath) + : handled(handled), action(action), resultFilePath(resultFilePath) + {} + + DownloadStartResponse::DownloadStartResponse(const flutter::EncodableMap& map) + : DownloadStartResponse(get_fl_map_value(map, "handled"), + DownloadStartResponseActionFromInteger(get_optional_fl_map_value(map, "action")), + get_optional_fl_map_value(map, "resultFilePath")) + {} + + flutter::EncodableMap DownloadStartResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"handled", make_fl_value(handled)}, + {"action", make_fl_value(DownloadStartResponseActionToInteger(action))}, + {"resultFilePath", make_fl_value(resultFilePath)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_response.h b/flutter_inappwebview_windows/windows/types/download_start_response.h new file mode 100644 index 000000000..62e4a984a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_response.h @@ -0,0 +1,60 @@ +#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 + }; + + inline std::optional DownloadStartResponseActionFromInteger(const std::optional& action) + { + if (action.has_value()) { + switch (action.value()) { + case 0: + return DownloadStartResponseAction::cancel; + default: + return DownloadStartResponseAction::cancel; + } + } + return std::optional{}; + } + + inline std::optional DownloadStartResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class DownloadStartResponse + { + public: + const bool handled; + const std::optional action; + const std::optional resultFilePath; + + DownloadStartResponse(const bool& handled, + const std::optional& action, + const std::optional& resultFilePath); + DownloadStartResponse(const flutter::EncodableMap& map); + ~DownloadStartResponse() = default; + + bool DownloadStartResponse::operator==(const DownloadStartResponse& other) + { + return handled == other.handled && + action == other.action && + resultFilePath == other.resultFilePath; + } + bool DownloadStartResponse::operator!=(const DownloadStartResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/frame_info.cpp b/flutter_inappwebview_windows/windows/types/frame_info.cpp new file mode 100644 index 000000000..7417a3a0d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/frame_info.cpp @@ -0,0 +1,79 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "frame_info.h" + +#include + +namespace flutter_inappwebview_plugin +{ + FrameInfo::FrameInfo(const bool& isMainFrame, + const std::optional> request, + const std::optional> securityOrigin, + const std::optional& name, + const std::optional& frameId, + const std::optional& kind) + : isMainFrame(isMainFrame), request(request), securityOrigin(securityOrigin), name(name), frameId(frameId), kind(kind) + {} + + flutter::EncodableMap FrameInfo::toEncodableMap() const + { + return flutter::EncodableMap{ + {"isMainFrame", make_fl_value(isMainFrame)}, + {"request", request.has_value() ? request.value()->toEncodableMap() : make_fl_value()}, + {"securityOrigin", securityOrigin.has_value() ? securityOrigin.value()->toEncodableMap() : make_fl_value()}, + {"name", make_fl_value(name)}, + {"frameId", make_fl_value(frameId)}, + {"kind", make_fl_value(kind)} + }; + } + + std::unique_ptr FrameInfo::fromICoreWebView2FrameInfo(const wil::com_ptr webViewFrameInfo) + { + wil::unique_cotaskmem_string url; + auto request = std::optional>{}; + auto securityOrigin = std::optional>{}; + if (succeededOrLog(webViewFrameInfo->get_Source(&url))) { + auto sourceUrl = wide_to_utf8(url.get()); + request = std::make_shared( + sourceUrl, + std::optional{}, + std::optional>{}, + std::optional>{} + ); + + if (!sourceUrl.empty()) { + try { + winrt::Windows::Foundation::Uri const uri{ url.get() }; + + securityOrigin = std::make_shared( + wide_to_utf8(uri.Host().c_str()), + uri.Port(), + wide_to_utf8(uri.SchemeName().c_str()) + ); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } + } + } + + auto webViewFrameInfo2 = webViewFrameInfo.try_query(); + + uint32_t frameId; + wil::unique_cotaskmem_string name; + COREWEBVIEW2_FRAME_KIND kind = COREWEBVIEW2_FRAME_KIND_UNKNOWN; + if (webViewFrameInfo2) { + failedLog(webViewFrameInfo2->get_FrameKind(&kind)); + } + + return std::make_unique( + webViewFrameInfo2 ? kind == COREWEBVIEW2_FRAME_KIND_MAIN_FRAME : false, + request, + securityOrigin, + succeededOrLog(webViewFrameInfo->get_Name(&name)) ? wide_to_utf8(name.get()) : std::optional{}, + webViewFrameInfo2 && succeededOrLog(webViewFrameInfo2->get_FrameId(&frameId)) ? frameId : std::optional{}, + webViewFrameInfo2 ? (int64_t)kind : std::optional{} + ); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/frame_info.h b/flutter_inappwebview_windows/windows/types/frame_info.h new file mode 100644 index 000000000..103c30e1a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/frame_info.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ + +#include +#include +#include +#include +#include + +#include "security_origin.h" +#include "url_request.h" + +namespace flutter_inappwebview_plugin +{ + + class FrameInfo + { + public: + const bool isMainFrame; + const std::optional> request; + const std::optional> securityOrigin; + const std::optional name; + const std::optional frameId; + const std::optional kind; + + FrameInfo(const bool& isMainFrame, + const std::optional> request, + const std::optional> securityOrigin, + const std::optional& name, + const std::optional& frameId, + const std::optional& kind); + ~FrameInfo() = default; + + flutter::EncodableMap toEncodableMap() const; + static std::unique_ptr fromICoreWebView2FrameInfo(const wil::com_ptr webViewFrameInfo); + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_auth_response.cpp b/flutter_inappwebview_windows/windows/types/http_auth_response.cpp new file mode 100644 index 000000000..672d02ab7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_auth_response.cpp @@ -0,0 +1,29 @@ +#include "../utils/flutter.h" +#include "http_auth_response.h" + +namespace flutter_inappwebview_plugin +{ + HttpAuthResponse::HttpAuthResponse(const std::string& username, + const std::string& password, + const bool& permanentPersistence, + const std::optional& action) + : username(username), password(password), permanentPersistence(permanentPersistence), action(action) + {} + + HttpAuthResponse::HttpAuthResponse(const flutter::EncodableMap& map) + : HttpAuthResponse(get_fl_map_value(map, "username"), + get_fl_map_value(map, "password"), + get_fl_map_value(map, "permanentPersistence"), + HttpAuthResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap HttpAuthResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)}, + {"permanentPersistence", make_fl_value(permanentPersistence)}, + {"action", make_fl_value(HttpAuthResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_auth_response.h b/flutter_inappwebview_windows/windows/types/http_auth_response.h new file mode 100644 index 000000000..f80ce57a5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_auth_response.h @@ -0,0 +1,69 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class HttpAuthResponseAction { + cancel = 0, + proceed, + useSavedHttpAuthCredentials // not supported currently + }; + + inline HttpAuthResponseAction HttpAuthResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return HttpAuthResponseAction::cancel; + } + switch (action.value()) { + case 0: + return HttpAuthResponseAction::cancel; + case 1: + return HttpAuthResponseAction::proceed; + case 2: + // not supported currently + // return HttpAuthResponseAction::useSavedHttpAuthCredentials; + default: + return HttpAuthResponseAction::cancel; + } + } + + inline std::optional HttpAuthResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class HttpAuthResponse + { + public: + const std::string username; + const std::string password; + const bool permanentPersistence; + const std::optional action; + + HttpAuthResponse(const std::string& username, + const std::string& password, + const bool& permanentPersistence, + const std::optional& action); + HttpAuthResponse(const flutter::EncodableMap& map); + ~HttpAuthResponse() = default; + + bool HttpAuthResponse::operator==(const HttpAuthResponse& other) + { + return username == other.username && password == other.password && + permanentPersistence == other.permanentPersistence && + action == other.action; + } + bool HttpAuthResponse::operator!=(const HttpAuthResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp new file mode 100644 index 000000000..6687469dd --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp @@ -0,0 +1,19 @@ +#include "../utils/flutter.h" +#include "http_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + HttpAuthenticationChallenge::HttpAuthenticationChallenge(const std::shared_ptr protectionSpace, + const int64_t& previousFailureCount, + const std::optional> proposedCredential) + : URLAuthenticationChallenge(protectionSpace), previousFailureCount(previousFailureCount), proposedCredential(proposedCredential) + {} + + flutter::EncodableMap HttpAuthenticationChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + map.insert({ "previousFailureCount", make_fl_value(previousFailureCount) }); + map.insert({ "proposedCredential", proposedCredential.has_value() ? proposedCredential.value()->toEncodableMap() : make_fl_value() }); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h new file mode 100644 index 000000000..ff2a26506 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ + +#include +#include + +#include "url_authentication_challenge.h" +#include "url_credential.h" + +namespace flutter_inappwebview_plugin +{ + class HttpAuthenticationChallenge : URLAuthenticationChallenge + { + public: + const int64_t previousFailureCount; + const std::optional> proposedCredential; + + HttpAuthenticationChallenge(const std::shared_ptr protectionSpace, + const int64_t& previousFailureCount, + const std::optional> proposedCredential); + ~HttpAuthenticationChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp new file mode 100644 index 000000000..af1683e84 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "../utils/string.h" +#include "javascript_handler_function_data.h" + +namespace flutter_inappwebview_plugin +{ + JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(const std::string& origin, const std::string& requestUrl, const bool& isMainFrame, const std::string& args) + : origin(origin), requestUrl(requestUrl), isMainFrame(isMainFrame), args(args) + {} + + JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(const flutter::EncodableMap& map) + : JavaScriptHandlerFunctionData(get_fl_map_value(map, "origin"), + get_fl_map_value(map, "requestUrl"), + get_fl_map_value(map, "isMainFrame"), + get_fl_map_value(map, "args")) + {} + + flutter::EncodableMap JavaScriptHandlerFunctionData::toEncodableMap() const + { + return flutter::EncodableMap{ + {"origin", make_fl_value(origin)}, + {"requestUrl", make_fl_value(requestUrl)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + {"args", make_fl_value(args)} + }; + } +} diff --git a/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h new file mode 100644 index 000000000..aa5a3f6b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h @@ -0,0 +1,25 @@ +#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, const bool& isMainFrame, const std::string& args); + JavaScriptHandlerFunctionData(const flutter::EncodableMap& map); + ~JavaScriptHandlerFunctionData() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.cpp b/flutter_inappwebview_windows/windows/types/navigation_action.cpp new file mode 100644 index 000000000..d8be38645 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/navigation_action.cpp @@ -0,0 +1,21 @@ +#include "../utils/flutter.h" +#include "navigation_action.h" + +namespace flutter_inappwebview_plugin +{ + NavigationAction::NavigationAction(std::shared_ptr request, const bool& isForMainFrame, + const std::optional& isRedirect, const std::optional& navigationType) + : request(std::move(request)), isForMainFrame(isForMainFrame), + isRedirect(isRedirect), navigationType(navigationType) + {} + + flutter::EncodableMap NavigationAction::toEncodableMap() const + { + return flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"isForMainFrame", isForMainFrame}, + {"isRedirect", make_fl_value(isRedirect)}, + {"navigationType", make_fl_value(NavigationActionTypeToInteger(navigationType))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.h b/flutter_inappwebview_windows/windows/types/navigation_action.h new file mode 100644 index 000000000..1dced9307 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/navigation_action.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ + +#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, const bool& isForMainFrame, const std::optional& isRedirect, const std::optional& navigationType); + ~NavigationAction() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/new_window_requested_args.cpp b/flutter_inappwebview_windows/windows/types/new_window_requested_args.cpp new file mode 100644 index 000000000..50c9f9474 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/new_window_requested_args.cpp @@ -0,0 +1,9 @@ +#include "new_window_requested_args.h" + +namespace flutter_inappwebview_plugin +{ + NewWindowRequestedArgs::NewWindowRequestedArgs(wil::com_ptr args, + wil::com_ptr deferral) + : args(std::move(args)), deferral(std::move(deferral)) + {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/new_window_requested_args.h b/flutter_inappwebview_windows/windows/types/new_window_requested_args.h new file mode 100644 index 000000000..781a854e4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/new_window_requested_args.h @@ -0,0 +1,20 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_NEW_WINDOW_REQUESTED_ARGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_NEW_WINDOW_REQUESTED_ARGS_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class NewWindowRequestedArgs + { + public: + wil::com_ptr args; + wil::com_ptr deferral; + + NewWindowRequestedArgs(wil::com_ptr args, wil::com_ptr deferral); + ~NewWindowRequestedArgs() = default; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_NEW_WINDOW_REQUESTED_ARGS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/permission_response.cpp b/flutter_inappwebview_windows/windows/types/permission_response.cpp new file mode 100644 index 000000000..b6b7d3dd0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/permission_response.cpp @@ -0,0 +1,21 @@ +#include "permission_response.h" + +namespace flutter_inappwebview_plugin +{ + PermissionResponse::PermissionResponse(const std::optional>& resources, const std::optional& action) + : resources(resources), action(action) + {} + + PermissionResponse::PermissionResponse(const flutter::EncodableMap& map) + : PermissionResponse(get_optional_fl_map_value>(map, "resources"), + PermissionResponseActionTypeFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap PermissionResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"resources", make_fl_value(resources)}, + {"action", make_fl_value(PermissionResponseActionTypeToInteger(action))}, + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/permission_response.h b/flutter_inappwebview_windows/windows/types/permission_response.h new file mode 100644 index 000000000..fd5cf7854 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/permission_response.h @@ -0,0 +1,60 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ + +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + enum class PermissionResponseActionType { + deny = 0, + grant, + prompt + }; + + inline PermissionResponseActionType PermissionResponseActionTypeFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return PermissionResponseActionType::prompt; + } + switch (action.value()) { + case 0: + return PermissionResponseActionType::deny; + case 1: + return PermissionResponseActionType::grant; + case 2: + default: + return PermissionResponseActionType::prompt; + } + } + + inline std::optional PermissionResponseActionTypeToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class PermissionResponse + { + public: + const std::optional> resources; + const std::optional action; + + PermissionResponse(const std::optional>& resources, const std::optional& action); + PermissionResponse(const flutter::EncodableMap& map); + ~PermissionResponse() = default; + + bool PermissionResponse::operator==(const PermissionResponse& other) + { + return resources == other.resources && action == other.action; + } + bool PermissionResponse::operator!=(const PermissionResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/physical_key_status.cpp b/flutter_inappwebview_windows/windows/types/physical_key_status.cpp new file mode 100644 index 000000000..5cbe2d072 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/physical_key_status.cpp @@ -0,0 +1,31 @@ +#include "../utils/flutter.h" +#include "physical_key_status.h" + +namespace flutter_inappwebview_plugin +{ + PhysicalKeyStatus::PhysicalKeyStatus(const int64_t& repeatCount, + const int64_t& scanCode, + const bool& isExtendedKey, + const bool& isMenuKeyDown, + const bool& wasKeyDown, + const bool& isKeyReleased) + : repeatCount(repeatCount), scanCode(scanCode), isExtendedKey(isExtendedKey), isMenuKeyDown(isMenuKeyDown), wasKeyDown(wasKeyDown), isKeyReleased(isKeyReleased) + {} + + std::unique_ptr PhysicalKeyStatus::fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(const COREWEBVIEW2_PHYSICAL_KEY_STATUS& status) + { + return std::make_unique(status.RepeatCount, status.ScanCode, status.IsExtendedKey, status.IsMenuKeyDown, status.WasKeyDown, status.IsKeyReleased); + } + + flutter::EncodableMap PhysicalKeyStatus::toEncodableMap() const + { + return flutter::EncodableMap{ + { "repeatCount", make_fl_value(repeatCount) }, + { "scanCode", make_fl_value(scanCode) }, + { "isExtendedKey", make_fl_value(isExtendedKey) }, + { "isMenuKeyDown", make_fl_value(isMenuKeyDown) }, + { "wasKeyDown", make_fl_value(wasKeyDown) }, + { "isKeyReleased", make_fl_value(isKeyReleased) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/physical_key_status.h b/flutter_inappwebview_windows/windows/types/physical_key_status.h new file mode 100644 index 000000000..51fbd33a8 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/physical_key_status.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class PhysicalKeyStatus + { + public: + const int64_t repeatCount; + const int64_t scanCode; + const bool isExtendedKey; + const bool isMenuKeyDown; + const bool wasKeyDown; + const bool isKeyReleased; + + PhysicalKeyStatus(const int64_t& repeatCount, + const int64_t& scanCode, + const bool& isExtendedKey, + const bool& isMenuKeyDown, + const bool& wasKeyDown, + const bool& isKeyReleased); + ~PhysicalKeyStatus() = default; + + static std::unique_ptr fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(const COREWEBVIEW2_PHYSICAL_KEY_STATUS& status); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.cpp b/flutter_inappwebview_windows/windows/types/plugin_script.cpp new file mode 100644 index 000000000..a292255ae --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/plugin_script.cpp @@ -0,0 +1,31 @@ +#include "plugin_script.h" + +namespace flutter_inappwebview_plugin +{ + PluginScript::PluginScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld, + const bool& requiredInAllContentWorlds + ) : UserScript(groupName, source, injectionTime, forMainFrameOnly, allowedOriginRules, std::move(contentWorld)), + requiredInAllContentWorlds_(requiredInAllContentWorlds) + {} + + std::shared_ptr PluginScript::copyAndSet(const std::shared_ptr cw) const + { + return std::make_unique( + this->groupName, + this->source, + this->injectionTime, + this->forMainFrameOnly, + this->allowedOriginRules, + cw, + this->requiredInAllContentWorlds_ + ); + } + + PluginScript::~PluginScript() {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.h b/flutter_inappwebview_windows/windows/types/plugin_script.h new file mode 100644 index 000000000..c8a6efe52 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/plugin_script.h @@ -0,0 +1,34 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ + +#include "user_script.h" + +namespace flutter_inappwebview_plugin +{ + class PluginScript : public UserScript + { + public: + + PluginScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld, + const bool& requiredInAllContentWorlds + ); + ~PluginScript(); + + bool isRequiredInAllContentWorlds() const + { + return requiredInAllContentWorlds_; + } + + std::shared_ptr copyAndSet(const std::shared_ptr cw) const; + + private: + bool requiredInAllContentWorlds_; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp b/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp new file mode 100644 index 000000000..be305a661 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp @@ -0,0 +1,26 @@ +#include "../utils/flutter.h" +#include "process_failed_detail.h" + +namespace flutter_inappwebview_plugin +{ + ProcessFailedDetail::ProcessFailedDetail(const int64_t& kind, + const std::optional& exitCode, + const std::optional& processDescription, + const std::optional& reason, + const std::optional& failureSourceModulePath, + const std::optional>>& frameInfos) + : kind(kind), exitCode(exitCode), processDescription(processDescription), reason(reason), failureSourceModulePath(failureSourceModulePath), frameInfos(frameInfos) + {} + + flutter::EncodableMap ProcessFailedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"exitCode", make_fl_value(exitCode)}, + {"processDescription", make_fl_value(processDescription)}, + {"reason", make_fl_value(reason)}, + {"failureSourceModulePath", make_fl_value(failureSourceModulePath)}, + {"frameInfos", frameInfos.has_value() ? make_fl_value(functional_map(frameInfos.value(), [](const std::shared_ptr& item) { return item->toEncodableMap(); })) : make_fl_value()} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/process_failed_detail.h b/flutter_inappwebview_windows/windows/types/process_failed_detail.h new file mode 100644 index 000000000..555ae4f69 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/process_failed_detail.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ + +#include +#include +#include +#include + +#include "frame_info.h" + +namespace flutter_inappwebview_plugin +{ + + class ProcessFailedDetail + { + public: + const int64_t kind; + const std::optional exitCode; + const std::optional processDescription; + const std::optional reason; + const std::optional failureSourceModulePath; + const std::optional>> frameInfos; + + ProcessFailedDetail(const int64_t& kind, + const std::optional& exitCode, + const std::optional& processDescription, + const std::optional& reason, + const std::optional& failureSourceModulePath, + const std::optional>>& frameInfos); + ~ProcessFailedDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/rect.cpp b/flutter_inappwebview_windows/windows/types/rect.cpp new file mode 100644 index 000000000..38fd176a1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/rect.cpp @@ -0,0 +1,25 @@ +#include "rect.h" + +namespace flutter_inappwebview_plugin +{ + Rect::Rect(const double& x, const double& y, const double& width, const double& height) + : x(x), y(y), width(width), height(height) + {} + + Rect::Rect(const flutter::EncodableMap& map) + : Rect(get_fl_map_value(map, "x"), + get_fl_map_value(map, "y"), + get_fl_map_value(map, "width"), + get_fl_map_value(map, "height")) + {} + + flutter::EncodableMap Rect::toEncodableMap() const + { + return flutter::EncodableMap{ + {"x", x}, + {"y", y}, + {"width", width}, + {"height", height} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/rect.h b/flutter_inappwebview_windows/windows/types/rect.h new file mode 100644 index 000000000..57c94eebc --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/rect.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class Rect + { + public: + const double x; + const double y; + const double width; + const double height; + + Rect(const double& x, const double& y, const double& width, const double& height); + Rect(const flutter::EncodableMap& map); + ~Rect() = default; + + bool Rect::operator==(const Rect& other) + { + return x == other.x && y == other.y && width == other.width && height == other.height; + } + bool Rect::operator!=(const Rect& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp new file mode 100644 index 000000000..8fbb1238a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp @@ -0,0 +1,16 @@ +#include "../utils/flutter.h" +#include "render_process_gone_detail.h" + +namespace flutter_inappwebview_plugin +{ + RenderProcessGoneDetail::RenderProcessGoneDetail(const bool& didCrash) + : didCrash(didCrash) + {} + + flutter::EncodableMap RenderProcessGoneDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"didCrash", make_fl_value(didCrash)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h new file mode 100644 index 000000000..3e324c1ae --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + + class RenderProcessGoneDetail + { + public: + const bool didCrash; + + RenderProcessGoneDetail(const bool& didCrash); + ~RenderProcessGoneDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp new file mode 100644 index 000000000..a8ac01fb7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp @@ -0,0 +1,48 @@ +#include "../utils/flutter.h" +#include "../utils/map.h" +#include "screenshot_configuration.h" + +namespace flutter_inappwebview_plugin +{ + CompressFormat CompressFormatFromString(const std::string& compressFormat) + { + if (string_equals(compressFormat, "PNG")) { + return CompressFormat::png; + } + else if (string_equals(compressFormat, "JPEG")) { + return CompressFormat::jpeg; + } + else if (string_equals(compressFormat, "WEBP")) { + return CompressFormat::webp; + } + return CompressFormat::png; + } + + std::string CompressFormatToString(const CompressFormat& compressFormat) + { + switch (compressFormat) { + case CompressFormat::jpeg: + return "JPEG"; + case CompressFormat::webp: + return "WEBP"; + case CompressFormat::png: + default: + return "PNG"; + } + } + + ScreenshotConfiguration::ScreenshotConfiguration( + const CompressFormat& compressFormat, + const int64_t& quality, + const std::optional> rect + ) : compressFormat(compressFormat), quality(quality), rect(rect) + {} + + ScreenshotConfiguration::ScreenshotConfiguration(const flutter::EncodableMap& map) + : ScreenshotConfiguration(CompressFormatFromString(get_fl_map_value(map, "compressFormat")), + get_fl_map_value(map, "quality"), + fl_map_contains_not_null(map, "rect") ? std::make_shared(get_fl_map_value(map, "rect")) : std::optional>{}) + {} + + ScreenshotConfiguration::~ScreenshotConfiguration() {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.h b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h new file mode 100644 index 000000000..b090bac69 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ +#define FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ + +#include +#include +#include + +#include "../types/rect.h" +#include "../utils/string.h" + +namespace flutter_inappwebview_plugin +{ + enum class CompressFormat { + png, + jpeg, + webp + }; + + CompressFormat CompressFormatFromString(const std::string& compressFormat); + std::string CompressFormatToString(const CompressFormat& compressFormat); + + class ScreenshotConfiguration + { + public: + const CompressFormat compressFormat; + const int64_t quality; + const std::optional> rect; + + ScreenshotConfiguration( + const CompressFormat& compressFormat, + const int64_t& quality, + const std::optional> rect + ); + ScreenshotConfiguration(const flutter::EncodableMap& map); + ~ScreenshotConfiguration(); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/security_origin.cpp b/flutter_inappwebview_windows/windows/types/security_origin.cpp new file mode 100644 index 000000000..bfd43a329 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/security_origin.cpp @@ -0,0 +1,18 @@ +#include "../utils/flutter.h" +#include "security_origin.h" + +namespace flutter_inappwebview_plugin +{ + SecurityOrigin::SecurityOrigin(const std::string& host, const int64_t& port, const std::string& protocol) + : host(host), port(port), protocol(protocol) + {} + + flutter::EncodableMap SecurityOrigin::toEncodableMap() const + { + return flutter::EncodableMap{ + {"host", make_fl_value(host)}, + {"port", make_fl_value(port)}, + {"protocol", make_fl_value(protocol)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/security_origin.h b/flutter_inappwebview_windows/windows/types/security_origin.h new file mode 100644 index 000000000..84c3fee28 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/security_origin.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + + class SecurityOrigin + { + public: + const std::string host; + const int64_t port; + const std::string protocol; + + SecurityOrigin(const std::string& host, const int64_t& port, const std::string& protocol); + ~SecurityOrigin() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp new file mode 100644 index 000000000..e502c0a9e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp @@ -0,0 +1,22 @@ +#include "../utils/flutter.h" +#include "server_trust_auth_response.h" + +namespace flutter_inappwebview_plugin +{ + ServerTrustAuthResponse::ServerTrustAuthResponse( + const std::optional& action) + : action(action) + {} + + ServerTrustAuthResponse::ServerTrustAuthResponse(const flutter::EncodableMap& map) + : ServerTrustAuthResponse( + ServerTrustAuthResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap ServerTrustAuthResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"action", make_fl_value(ServerTrustAuthResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h new file mode 100644 index 000000000..af2c7ba2e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h @@ -0,0 +1,55 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class ServerTrustAuthResponseAction { + cancel = 0, + proceed + }; + + inline ServerTrustAuthResponseAction ServerTrustAuthResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return ServerTrustAuthResponseAction::cancel; + } + switch (action.value()) { + case 1: + return ServerTrustAuthResponseAction::proceed; + case 0: + default: + return ServerTrustAuthResponseAction::cancel; + } + } + + inline std::optional ServerTrustAuthResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class ServerTrustAuthResponse + { + public: + const std::optional action; + + ServerTrustAuthResponse(const std::optional& action); + ServerTrustAuthResponse(const flutter::EncodableMap& map); + ~ServerTrustAuthResponse() = default; + + bool ServerTrustAuthResponse::operator==(const ServerTrustAuthResponse& other) + { + return action == other.action; + } + bool ServerTrustAuthResponse::operator!=(const ServerTrustAuthResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp b/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp new file mode 100644 index 000000000..d20c8c285 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp @@ -0,0 +1,15 @@ +#include "../utils/flutter.h" +#include "server_trust_challenge.h" + +namespace flutter_inappwebview_plugin +{ + ServerTrustChallenge::ServerTrustChallenge(const std::shared_ptr protectionSpace) + : URLAuthenticationChallenge(protectionSpace) + {} + + flutter::EncodableMap ServerTrustChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_challenge.h b/flutter_inappwebview_windows/windows/types/server_trust_challenge.h new file mode 100644 index 000000000..9121ec800 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_challenge.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ + +#include +#include + +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + class ServerTrustChallenge : URLAuthenticationChallenge + { + public: + ServerTrustChallenge(const std::shared_ptr protectionSpace); + ~ServerTrustChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/size_2d.cpp b/flutter_inappwebview_windows/windows/types/size_2d.cpp new file mode 100644 index 000000000..7a790854d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/size_2d.cpp @@ -0,0 +1,21 @@ +#include "size_2d.h" + +namespace flutter_inappwebview_plugin +{ + Size2D::Size2D(const double& width, const double& height) + : width(width), height(height) + {} + + Size2D::Size2D(const flutter::EncodableMap& map) + : Size2D(get_fl_map_value(map, "width"), + get_fl_map_value(map, "height")) + {} + + flutter::EncodableMap Size2D::toEncodableMap() const + { + return flutter::EncodableMap{ + {"width", width}, + {"height", height} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/size_2d.h b/flutter_inappwebview_windows/windows/types/size_2d.h new file mode 100644 index 000000000..e2cc28f67 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/size_2d.h @@ -0,0 +1,34 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class Size2D + { + public: + const double width; + const double height; + + Size2D(const double& width, const double& height); + Size2D(const flutter::EncodableMap& map); + ~Size2D() = default; + + bool Size2D::operator==(const Size2D& other) + { + return width == other.width && height == other.height; + } + bool Size2D::operator!=(const Size2D& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/ssl_certificate.cpp b/flutter_inappwebview_windows/windows/types/ssl_certificate.cpp new file mode 100644 index 000000000..8cd2f37c3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_certificate.cpp @@ -0,0 +1,16 @@ +#include "../utils/flutter.h" +#include "ssl_certificate.h" + +namespace flutter_inappwebview_plugin +{ + SslCertificate::SslCertificate(std::string x509Certificate) + : x509Certificate(x509Certificate) + {} + + flutter::EncodableMap SslCertificate::toEncodableMap() const + { + return flutter::EncodableMap{ + {"x509Certificate", make_fl_value(x509Certificate)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/ssl_certificate.h b/flutter_inappwebview_windows/windows/types/ssl_certificate.h new file mode 100644 index 000000000..649978cd0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_certificate.h @@ -0,0 +1,20 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + class SslCertificate + { + public: + const std::string x509Certificate; + + SslCertificate(std::string x509Certificate); + ~SslCertificate() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_CERTIFICATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/ssl_error.cpp b/flutter_inappwebview_windows/windows/types/ssl_error.cpp new file mode 100644 index 000000000..1147862d0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_error.cpp @@ -0,0 +1,17 @@ +#include "../utils/flutter.h" +#include "ssl_error.h" + +namespace flutter_inappwebview_plugin +{ + SslError::SslError(const COREWEBVIEW2_WEB_ERROR_STATUS& code, const std::optional& message) + : code(code), message(message) + {} + + flutter::EncodableMap SslError::toEncodableMap() const + { + return flutter::EncodableMap{ + {"code", make_fl_value((int64_t)code)}, + {"message", make_fl_value(message)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/ssl_error.h b/flutter_inappwebview_windows/windows/types/ssl_error.h new file mode 100644 index 000000000..a86204577 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_error.h @@ -0,0 +1,44 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ + +#include +#include +#include + +#include "WebView2.h" + +namespace flutter_inappwebview_plugin +{ + inline std::optional COREWEBVIEW2_WEB_ERROR_STATUS_ToString(const COREWEBVIEW2_WEB_ERROR_STATUS& code) + { + switch (code) { + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT: + return "Indicates that the SSL certificate common name does not match the web address."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED: + return "Indicates that the SSL certificate has expired."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS: + return "Indicates that the SSL client certificate contains errors."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED: + return "Indicates that the SSL certificate has been revoked."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID: + return "Indicates that the SSL certificate is not valid."; + default: + break; + } + return std::optional{}; + } + + class SslError + { + public: + const COREWEBVIEW2_WEB_ERROR_STATUS code; + const std::optional message; + + SslError(const COREWEBVIEW2_WEB_ERROR_STATUS& code, const std::optional& message); + ~SslError() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp new file mode 100644 index 000000000..7b1a5ff72 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp @@ -0,0 +1,16 @@ +#include "../utils/flutter.h" +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + URLAuthenticationChallenge::URLAuthenticationChallenge(const std::shared_ptr protectionSpace) + : protectionSpace(protectionSpace) + {} + + flutter::EncodableMap URLAuthenticationChallenge::toEncodableMap() const + { + return flutter::EncodableMap{ + {"protectionSpace", protectionSpace->toEncodableMap()}, + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h new file mode 100644 index 000000000..80688107f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h @@ -0,0 +1,22 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ + +#include + +#include "url_protection_space.h" + +namespace flutter_inappwebview_plugin +{ + class URLAuthenticationChallenge + { + public: + const std::shared_ptr protectionSpace; + + URLAuthenticationChallenge(const std::shared_ptr protectionSpace); + ~URLAuthenticationChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_credential.cpp b/flutter_inappwebview_windows/windows/types/url_credential.cpp new file mode 100644 index 000000000..f02c7565d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_credential.cpp @@ -0,0 +1,18 @@ +#include "../utils/flutter.h" +#include "url_credential.h" + +namespace flutter_inappwebview_plugin +{ + URLCredential::URLCredential(const std::optional& username, + const std::optional& password) + : username(username), password(password) + {} + + flutter::EncodableMap URLCredential::toEncodableMap() const + { + return flutter::EncodableMap{ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_credential.h b/flutter_inappwebview_windows/windows/types/url_credential.h new file mode 100644 index 000000000..2f40bb7d4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_credential.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class URLCredential + { + public: + const std::optional username; + const std::optional password; + + URLCredential(const std::optional& username, + const std::optional& password); + ~URLCredential() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_protection_space.cpp b/flutter_inappwebview_windows/windows/types/url_protection_space.cpp new file mode 100644 index 000000000..26d62af97 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_protection_space.cpp @@ -0,0 +1,24 @@ +#include "../utils/flutter.h" +#include "url_protection_space.h" + +namespace flutter_inappwebview_plugin +{ + URLProtectionSpace::URLProtectionSpace(const std::string& host, const std::string& protocol, + const std::optional& realm, const int64_t& port, + const std::optional> sslCertificate, + const std::optional> sslError) + : host(host), protocol(protocol), realm(realm), port(port), sslCertificate(sslCertificate), sslError(sslError) + {} + + flutter::EncodableMap URLProtectionSpace::toEncodableMap() const + { + return flutter::EncodableMap{ + {"host", make_fl_value(host)}, + {"protocol", make_fl_value(protocol)}, + {"realm", make_fl_value(realm)}, + {"port", make_fl_value(port)}, + {"sslCertificate", sslCertificate.has_value() ? sslCertificate.value()->toEncodableMap() : make_fl_value()}, + {"sslError", sslError.has_value() ? sslError.value()->toEncodableMap() : make_fl_value()}, + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_protection_space.h b/flutter_inappwebview_windows/windows/types/url_protection_space.h new file mode 100644 index 000000000..44265c0b6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_protection_space.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ + +#include +#include +#include + +#include "ssl_certificate.h" +#include "ssl_error.h" + +namespace flutter_inappwebview_plugin +{ + class URLProtectionSpace + { + public: + const std::string host; + const std::string protocol; + const std::optional realm; + const int64_t port; + const std::optional> sslCertificate; + const std::optional> sslError; + + URLProtectionSpace(const std::string& host, const std::string& protocol, + const std::optional& realm, const int64_t& port, + const std::optional> sslCertificate, + const std::optional> sslError); + ~URLProtectionSpace() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_request.cpp b/flutter_inappwebview_windows/windows/types/url_request.cpp new file mode 100644 index 000000000..7335b0bd7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_request.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "url_request.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(const flutter::EncodableMap& map) + : URLRequest(get_optional_fl_map_value(map, "url"), + get_optional_fl_map_value(map, "method"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value>(map, "body")) + {} + + flutter::EncodableMap URLRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"body", make_fl_value(body)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_request.h b/flutter_inappwebview_windows/windows/types/url_request.h new file mode 100644 index 000000000..ded69b6e4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_request.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ + +#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(const flutter::EncodableMap& map); + ~URLRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/user_script.cpp b/flutter_inappwebview_windows/windows/types/user_script.cpp new file mode 100644 index 000000000..c74d705b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/user_script.cpp @@ -0,0 +1,32 @@ +#include "user_script.h" + +#include "../utils/log.h" +#include "../utils/string.h" + +namespace flutter_inappwebview_plugin +{ + UserScript::UserScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld + ) : groupName(groupName), source(source), + injectionTime(injectionTime), forMainFrameOnly(forMainFrameOnly), allowedOriginRules(allowedOriginRules), contentWorld(std::move(contentWorld)) + {} + + UserScript::UserScript(const flutter::EncodableMap& map) + : UserScript(get_optional_fl_map_value(map, "groupName"), + get_fl_map_value(map, "source"), + static_cast(get_fl_map_value(map, "injectionTime")), + get_fl_map_value(map, "forMainFrameOnly"), + fl_map_contains_not_null(map, "allowedOriginRules") ? + functional_map(get_fl_map_value(map, "allowedOriginRules"), [](const flutter::EncodableValue& m) { return std::get(m); }) + : std::optional>{}, + std::make_shared(get_fl_map_value(map, "contentWorld"))) + {} + + UserScript::~UserScript() + {} +} diff --git a/flutter_inappwebview_windows/windows/types/user_script.h b/flutter_inappwebview_windows/windows/types/user_script.h new file mode 100644 index 000000000..1ea9d472d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/user_script.h @@ -0,0 +1,42 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ + +#include +#include +#include +#include + +#include "../utils/flutter.h" +#include "content_world.h" + +namespace flutter_inappwebview_plugin +{ + enum class UserScriptInjectionTime { + atDocumentStart = 0, + atDocumentEnd + }; + + class UserScript + { + public: + std::string id; + const std::optional groupName; + const std::string source; + const UserScriptInjectionTime injectionTime; + const bool forMainFrameOnly; + const std::optional> allowedOriginRules; + const std::shared_ptr contentWorld; + + UserScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, + std::shared_ptr contentWorld + ); + UserScript(const flutter::EncodableMap& map); + ~UserScript(); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history.cpp b/flutter_inappwebview_windows/windows/types/web_history.cpp new file mode 100644 index 000000000..1790b61f7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history.cpp @@ -0,0 +1,22 @@ +#include "../utils/vector.h" +#include "web_history.h" + +namespace flutter_inappwebview_plugin +{ + WebHistory::WebHistory(const std::optional currentIndex, const std::optional>>& list) + : currentIndex(currentIndex), list(list) + {} + + WebHistory::WebHistory(const flutter::EncodableMap& map) + : WebHistory(get_optional_fl_map_value(map, "currentIndex"), + functional_map(get_optional_fl_map_value(map, "list"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })) + {} + + flutter::EncodableMap WebHistory::toEncodableMap() const + { + return flutter::EncodableMap{ + {"currentIndex", make_fl_value(currentIndex)}, + {"list", make_fl_value(functional_map(list, [](const std::shared_ptr& item) { return item->toEncodableMap(); }))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history.h b/flutter_inappwebview_windows/windows/types/web_history.h new file mode 100644 index 000000000..95c41bad9 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ + +#include +#include + +#include "../utils/flutter.h" +#include "web_history_item.h" + +namespace flutter_inappwebview_plugin +{ + class WebHistory + { + public: + const std::optional currentIndex; + const std::optional>> list; + + WebHistory(const std::optional currentIndex, const std::optional>>& list); + WebHistory(const flutter::EncodableMap& map); + ~WebHistory() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history_item.cpp b/flutter_inappwebview_windows/windows/types/web_history_item.cpp new file mode 100644 index 000000000..d2849df82 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.cpp @@ -0,0 +1,31 @@ +#include "web_history_item.h" + +namespace flutter_inappwebview_plugin +{ + WebHistoryItem::WebHistoryItem(const std::optional& entryId, const std::optional& index, const std::optional& offset, + const std::optional& originalUrl, const std::optional& title, + const std::optional& url) + : entryId(entryId), index(index), offset(offset), originalUrl(originalUrl), title(title), url(url) + {} + + WebHistoryItem::WebHistoryItem(const flutter::EncodableMap& map) + : WebHistoryItem(get_optional_fl_map_value(map, "entryId"), + get_optional_fl_map_value(map, "index"), + get_optional_fl_map_value(map, "offset"), + get_optional_fl_map_value(map, "originalUrl"), + get_optional_fl_map_value(map, "title"), + get_optional_fl_map_value(map, "url")) + {} + + flutter::EncodableMap WebHistoryItem::toEncodableMap() const + { + return flutter::EncodableMap{ + {"entryId", make_fl_value(entryId)}, + {"index", make_fl_value(index)}, + {"offset", make_fl_value(offset)}, + {"originalUrl", make_fl_value(originalUrl)}, + {"title", make_fl_value(title)}, + {"url", make_fl_value(url)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history_item.h b/flutter_inappwebview_windows/windows/types/web_history_item.h new file mode 100644 index 000000000..e58181e38 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class WebHistoryItem + { + public: + const std::optional entryId; + const std::optional index; + const std::optional offset; + const std::optional originalUrl; + const std::optional title; + const std::optional url; + + WebHistoryItem(const std::optional& entryId, const std::optional& index, const std::optional& offset, + const std::optional& originalUrl, const std::optional& title, + const std::optional& url); + WebHistoryItem(const flutter::EncodableMap& map); + ~WebHistoryItem() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_error.cpp b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp new file mode 100644 index 000000000..5f674d597 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp @@ -0,0 +1,22 @@ +#include "../utils/flutter.h" +#include "web_resource_error.h" + +namespace flutter_inappwebview_plugin +{ + WebResourceError::WebResourceError(const std::string& description, const int64_t type) + : description(description), type(type) + {} + + WebResourceError::WebResourceError(const flutter::EncodableMap& map) + : WebResourceError(get_fl_map_value(map, "description"), + get_fl_map_value(map, "type")) + {} + + flutter::EncodableMap WebResourceError::toEncodableMap() const + { + return flutter::EncodableMap{ + {"description", make_fl_value(description)}, + {"type", make_fl_value(type)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_error.h b/flutter_inappwebview_windows/windows/types/web_resource_error.h new file mode 100644 index 000000000..f5a0a5aa6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_error.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + static const std::string WebErrorStatusDescription[] = + { + "Indicates that an unknown error occurred.", + "Indicates that the SSL certificate common name does not match the web address.", + "Indicates that the SSL certificate has expired.", + "Indicates that the SSL client certificate contains errors.", + "Indicates that the SSL certificate has been revoked.", + "Indicates that the SSL certificate is not valid.", + "Indicates that the host is unreachable.", + "Indicates that the connection has timed out.", + "Indicates that the server returned an invalid or unrecognized response.", + "Indicates that the connection was stopped.", + "Indicates that the connection was reset.", + "Indicates that the Internet connection has been lost.", + "Indicates that a connection to the destination was not established.", + "Indicates that the provided host name was not able to be resolved.", + "Indicates that the operation was canceled.", + "Indicates that the request redirect failed.", + "Indicates that an unexpected error occurred.", + "Indicates that user is prompted with a login, waiting on user action.", + "Indicates that user lacks proper authentication credentials for a proxy server.", + }; + + class WebResourceError + { + public: + const std::string description; + const int64_t type; + + WebResourceError(const std::string& description, const int64_t type); + WebResourceError(const flutter::EncodableMap& map); + ~WebResourceError() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_request.cpp b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp new file mode 100644 index 000000000..42896c204 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp @@ -0,0 +1,59 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "web_resource_request.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(const flutter::EncodableMap& map) + : WebResourceRequest(get_optional_fl_map_value(map, "url"), + get_optional_fl_map_value(map, "method"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value(map, "isForMainFrame")) + {} + + WebResourceRequest::WebResourceRequest(wil::com_ptr webResourceRequest) + { + wil::unique_cotaskmem_string uri; + url = SUCCEEDED(webResourceRequest->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + + wil::unique_cotaskmem_string methodStr; + method = SUCCEEDED(webResourceRequest->get_Method(&methodStr)) ? wide_to_utf8(methodStr.get()) : std::optional{}; + + // Get the headers + wil::com_ptr requestHeaders; + if (SUCCEEDED(webResourceRequest->get_Headers(&requestHeaders))) { + std::map headersMap; + wil::com_ptr iterator; + if (SUCCEEDED(requestHeaders->GetIterator(&iterator))) { + BOOL hasCurrent = FALSE; + iterator->get_HasCurrentHeader(&hasCurrent); + while (hasCurrent) { + wil::unique_cotaskmem_string name, value; + iterator->GetCurrentHeader(&name, &value); + headersMap.emplace(wide_to_utf8(name.get()), wide_to_utf8(value.get())); + iterator->MoveNext(&hasCurrent); + } + if (!headersMap.empty()) { + headers = headersMap; + } + } + } + + isForMainFrame = true; + } + + flutter::EncodableMap WebResourceRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"isForMainFrame", make_fl_value(isForMainFrame)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_request.h b/flutter_inappwebview_windows/windows/types/web_resource_request.h new file mode 100644 index 000000000..39f559a89 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_request.h @@ -0,0 +1,29 @@ +#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(const flutter::EncodableMap& map); + WebResourceRequest(wil::com_ptr webResourceRequest); + ~WebResourceRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_response.cpp b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp new file mode 100644 index 000000000..430cf1afe --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp @@ -0,0 +1,80 @@ +#include "../utils/flutter.h" +#include "../utils/strconv.h" +#include "web_resource_response.h" + +#include + +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(const flutter::EncodableMap& map) + : WebResourceResponse(get_optional_fl_map_value(map, "contentType"), + get_optional_fl_map_value(map, "contentEncoding"), + get_optional_fl_map_value(map, "statusCode"), + get_optional_fl_map_value(map, "reasonPhrase"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value>(map, "data")) + {} + + flutter::EncodableMap WebResourceResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"contentEncoding", make_fl_value(contentEncoding)}, + {"contentType", make_fl_value(contentType)}, + {"statusCode", make_fl_value(statusCode)}, + {"reasonPhrase", make_fl_value(reasonPhrase)}, + {"headers", make_fl_value(headers)}, + {"data", make_fl_value(data)} + }; + } + + ICoreWebView2WebResourceResponse* WebResourceResponse::toWebView2Response(const wil::com_ptr webViewEnvironment) const + { + wil::com_ptr webResourceResponse; + + if (webViewEnvironment) { + wil::com_ptr postDataStream = nullptr; + if (data.has_value()) { + auto postData = std::string(data.value().begin(), data.value().end()); + postDataStream = SHCreateMemStream( + reinterpret_cast(postData.data()), static_cast(postData.length())); + } + + webViewEnvironment->CreateWebResourceResponse( + postDataStream.get(), + statusCode.value_or(200), // Default to 200 if statusCode is not set + reasonPhrase.has_value() ? utf8_to_wide(reasonPhrase.value()).c_str() : L"OK", // Default to "OK" if reasonPhrase is not set + nullptr, + &webResourceResponse); + + wil::com_ptr responseHeaders; + if (SUCCEEDED(webResourceResponse->get_Headers(&responseHeaders))) { + // Set the headers + if (headers.has_value()) { + for (auto const& [key, val] : headers.value()) { + responseHeaders->AppendHeader(utf8_to_wide(key).c_str(), utf8_to_wide(val).c_str()); + } + } + if (contentType.has_value() && !contentType.value().empty()) { + responseHeaders->AppendHeader(L"Content-Type", utf8_to_wide(contentType.value()).c_str()); + } + if (contentEncoding.has_value() && !contentEncoding.value().empty()) { + responseHeaders->AppendHeader(L"Content-Encoding", utf8_to_wide(contentEncoding.value()).c_str()); + } + } + + webResourceResponse->AddRef(); + } + + return webResourceResponse.get(); + } +} diff --git a/flutter_inappwebview_windows/windows/types/web_resource_response.h b/flutter_inappwebview_windows/windows/types/web_resource_response.h new file mode 100644 index 000000000..0cc4ed528 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_response.h @@ -0,0 +1,35 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class WebResourceResponse + { + public: + const std::optional contentType; + const std::optional contentEncoding; + const std::optional statusCode; + const std::optional reasonPhrase; + const std::optional> headers; + const std::optional> data; + + 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); + WebResourceResponse(const flutter::EncodableMap& map); + ~WebResourceResponse() = default; + + flutter::EncodableMap toEncodableMap() const; + ICoreWebView2WebResourceResponse* WebResourceResponse::toWebView2Response(const wil::com_ptr webViewEnvironment) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/window_features.cpp b/flutter_inappwebview_windows/windows/types/window_features.cpp new file mode 100644 index 000000000..513528df0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/window_features.cpp @@ -0,0 +1,63 @@ +#include "../utils/flutter.h" +#include "window_features.h" + +namespace flutter_inappwebview_plugin +{ + WindowFeatures::WindowFeatures(const std::optional& width, + const std::optional& height, + const std::optional& x, + const std::optional& y, + const std::optional& menuBarVisibility, + const std::optional& statusBarVisibility, + const std::optional& toolbarsVisibility) + : width(width), height(height), x(x), y(y), + menuBarVisibility(menuBarVisibility), statusBarVisibility(statusBarVisibility), + toolbarsVisibility(toolbarsVisibility) + {} + + WindowFeatures::WindowFeatures(const wil::com_ptr features) + { + UINT32 _x = 0; + UINT32 _y = 0; + UINT32 _height = 0; + UINT32 _width = 0; + BOOL _menuBarVisibility = FALSE; + BOOL _statusBarVisibility = FALSE; + BOOL _toolbarsVisibility = FALSE; + + if (SUCCEEDED(features->get_Left(&_x))) { + x = static_cast(_x); + } + if (SUCCEEDED(features->get_Top(&_y))) { + y = static_cast(_y); + } + if (SUCCEEDED(features->get_Height(&_height))) { + height = static_cast(_height); + } + if (SUCCEEDED(features->get_Width(&_width))) { + width = static_cast(_width); + } + if (SUCCEEDED(features->get_ShouldDisplayMenuBar(&_menuBarVisibility))) { + menuBarVisibility = static_cast(_menuBarVisibility); + } + if (SUCCEEDED(features->get_ShouldDisplayStatus(&_statusBarVisibility))) { + statusBarVisibility = static_cast(_statusBarVisibility); + } + if (SUCCEEDED(features->get_ShouldDisplayToolbar(&_toolbarsVisibility))) { + toolbarsVisibility = static_cast(_toolbarsVisibility); + } + } + + flutter::EncodableMap WindowFeatures::toEncodableMap() const + { + return flutter::EncodableMap{ + {"width", make_fl_value(width)}, + {"height", make_fl_value(height)}, + {"x", make_fl_value(x)}, + {"y", make_fl_value(y)}, + {"menuBarVisibility", make_fl_value(menuBarVisibility)}, + {"statusBarVisibility", make_fl_value(statusBarVisibility)}, + {"toolbarsVisibility", make_fl_value(toolbarsVisibility)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/window_features.h b/flutter_inappwebview_windows/windows/types/window_features.h new file mode 100644 index 000000000..9c5f124db --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/window_features.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_FEATURES_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_FEATURES_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class WindowFeatures + { + public: + std::optional width; + std::optional height; + std::optional x; + std::optional y; + std::optional menuBarVisibility; + std::optional statusBarVisibility; + std::optional toolbarsVisibility; + + WindowFeatures(const std::optional& width, + const std::optional& height, + const std::optional& x, + const std::optional& y, + const std::optional& menuBarVisibility, + const std::optional& statusBarVisibility, + const std::optional& toolbarsVisibility); + + WindowFeatures(wil::com_ptr features); + + ~WindowFeatures() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WINDOW_FEATURES_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/base64.cpp b/flutter_inappwebview_windows/windows/utils/base64.cpp new file mode 100644 index 000000000..938caae36 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/base64.cpp @@ -0,0 +1,296 @@ +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" + +#include +#include + +// +// Depending on the url parameter in base64_chars, one of +// two sets of base64 characters needs to be chosen. +// They differ in their last two characters. +// +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_" }; + +static unsigned int pos_of_char(const unsigned char chr) +{ + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + +static std::string insert_linebreaks(std::string str, size_t distance) +{ + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return ""; + } + + size_t pos = distance; + + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) +{ + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) +{ + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) +{ + return encode_with_line_breaks(s); +} + +template +static std::string encode(String s, bool url) +{ + return base64_encode(reinterpret_cast(s.data()), s.length(), url); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) +{ + + size_t len_encoded = (in_len + 2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos + 1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos + 2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + + return ret; +} + +template +static std::string decode(String const& encoded_string, bool remove_linebreaks) +{ + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) return std::string(); + + if (remove_linebreaks) { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos + 1)); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast(((pos_of_char(encoded_string.at(pos + 0))) << 2) + ((pos_of_char_1 & 0x30) >> 4))); + + if ((pos + 2 < length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos + 2) != '=' && + encoded_string.at(pos + 2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos + 2)); + ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); + + if ((pos + 3 < length_of_string) && + encoded_string.at(pos + 3) != '=' && + encoded_string.at(pos + 3) != '.' + ) { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string.at(pos + 3)))); + } + } + + pos += 4; + } + + return ret; +} + +std::string base64_decode(std::string const& s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); +} + +std::string base64_encode(std::string const& s, bool url) +{ + return encode(s, url); +} + +std::string base64_encode_pem(std::string const& s) +{ + return encode_pem(s); +} + +std::string base64_encode_mime(std::string const& s) +{ + return encode_mime(s); +} + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// + +std::string base64_encode(std::string_view s, bool url) +{ + return encode(s, url); +} + +std::string base64_encode_pem(std::string_view s) +{ + return encode_pem(s); +} + +std::string base64_encode_mime(std::string_view s) +{ + return encode_mime(s); +} + +std::string base64_decode(std::string_view s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); +} + +#endif // __cplusplus >= 201703L diff --git a/flutter_inappwebview_windows/windows/utils/base64.h b/flutter_inappwebview_windows/windows/utils/base64.h new file mode 100644 index 000000000..5e74e2f70 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/base64.h @@ -0,0 +1,35 @@ +// +// base64 encoding and decoding with C++. +// Version: 2.rc.09 (release candidate) +// + +#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A + +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +std::string base64_encode(std::string const& s, bool url = false); +std::string base64_encode_pem(std::string const& s); +std::string base64_encode_mime(std::string const& s); + +std::string base64_decode(std::string const& s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const*, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode(std::string_view s, bool url = false); +std::string base64_encode_pem(std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ diff --git a/flutter_inappwebview_windows/windows/utils/defer.h b/flutter_inappwebview_windows/windows/utils/defer.h new file mode 100644 index 000000000..80a6b6ba2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/defer.h @@ -0,0 +1,15 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + static inline std::shared_ptr defer(void* handle, const std::function& callback) + { + return std::shared_ptr(handle, callback); + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/flutter.h b/flutter_inappwebview_windows/windows/utils/flutter.h new file mode 100644 index 000000000..b3f03339e --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/flutter.h @@ -0,0 +1,197 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ + +#include + +#include "map.h" +#include "util.h" +#include "vector.h" + +namespace flutter_inappwebview_plugin +{ + static inline flutter::EncodableValue make_fl_value() + { + return flutter::EncodableValue(); + } + + template + static inline flutter::EncodableValue make_fl_value(const T& val) + { + return flutter::EncodableValue(val); + } + + template + static inline flutter::EncodableValue make_fl_value(const T* val) + { + return val == nullptr ? make_fl_value() : flutter::EncodableValue(val); + } + + template + static inline flutter::EncodableValue make_fl_value(const std::vector& vec) + { + auto encodableList = flutter::EncodableList{}; + for (auto const& val : vec) { + encodableList.push_back(make_fl_value(val)); + } + return encodableList; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::map& map) + { + auto encodableMap = flutter::EncodableMap{}; + for (auto const& [key, val] : map) { + encodableMap.insert({ make_fl_value(key), make_fl_value(val) }); + } + return encodableMap; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional& optional) + { + return optional.has_value() ? make_fl_value(optional.value()) : make_fl_value(); + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional>& optional) + { + if (!optional.has_value()) { + return make_fl_value(); + } + auto& vecValue = optional.value(); + auto encodableList = flutter::EncodableList{}; + for (auto const& val : vecValue) { + encodableList.push_back(make_fl_value(val)); + } + return encodableList; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional>& optional) + { + if (!optional.has_value()) { + return make_fl_value(); + } + auto& mapValue = optional.value(); + auto encodableMap = flutter::EncodableMap{}; + for (auto const& [key, val] : mapValue) { + encodableMap.insert({ make_fl_value(key), make_fl_value(val) }); + } + return encodableMap; + } + + static inline bool fl_map_contains(const flutter::EncodableMap& map, const char* key) + { + return map_contains(map, make_fl_value(key)); + } + + static inline bool fl_map_contains_not_null(const flutter::EncodableMap& map, const char* key) + { + return fl_map_contains(map, key) && !map.at(make_fl_value(key)).IsNull(); + } + + template::value && !std::is_same::value), bool>::type* = nullptr> + static inline T get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return std::get(map.at(make_fl_value(key))); + } + + template::value, bool>::type* = nullptr> + static inline int64_t get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return map.at(make_fl_value(key)).LongValue(); + } + + template::value, bool>::type* = nullptr> + static inline int64_t get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return map.at(make_fl_value(key)).LongValue(); + } + + template::value && !is_vector::value && !std::is_same::value && !std::is_same::value) || + std::is_same::value || std::is_same::value), int>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + if (fl_map_contains_not_null(map, key)) { + auto fl_key = make_fl_value(key); + return make_pointer_optional(std::get_if(&map.at(fl_key))); + } + return std::nullopt; + } + + template::value, bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + auto fl_key = make_fl_value(key); + if (fl_map_contains_not_null(map, key) && (std::holds_alternative(map.at(fl_key)) || std::holds_alternative(map.at(fl_key)))) { + return std::make_optional(map.at(fl_key).LongValue()); + } + return std::nullopt; + } + + template::value, bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + auto fl_key = make_fl_value(key); + if (fl_map_contains_not_null(map, key) && (std::holds_alternative(map.at(fl_key)) || std::holds_alternative(map.at(fl_key)))) { + return std::make_optional(map.at(fl_key).LongValue()); + } + return std::nullopt; + } + + template + static inline T get_fl_map_value(const flutter::EncodableMap& map, const char* key, const T& defaultValue) + { + auto optional = get_optional_fl_map_value(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } + + template::value && !std::is_same::value)>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + using K = typename T::key_type; + using V = typename T::mapped_type; + + auto flMap = std::get_if(&map.at(make_fl_value(key))); + if (flMap) { + T mapValue = {}; + for (auto itr = flMap->begin(); itr != flMap->end(); itr++) { + mapValue.insert({ std::get(itr->first), std::get(itr->second) }); + } + return make_pointer_optional(&mapValue); + } + return std::nullopt; + } + + template + static inline std::map get_fl_map_value(const flutter::EncodableMap& map, const char* key, const std::map& defaultValue) + { + auto optional = get_optional_fl_map_value>(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } + + template::value && !std::is_same::value), bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + using V = typename T::value_type; + + auto flList = std::get_if(&map.at(make_fl_value(key))); + if (flList) { + T vecValue; + for (auto itr = flList->begin(); itr != flList->end(); itr++) { + vecValue.push_back(std::get(*itr)); + } + return make_pointer_optional(&vecValue); + } + return std::nullopt; + } + + template + static inline std::vector get_fl_map_value(const flutter::EncodableMap& map, const char* key, const std::vector& defaultValue) + { + auto optional = get_optional_fl_map_value>(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/log.h b/flutter_inappwebview_windows/windows/utils/log.h new file mode 100644 index 000000000..e61dcb61c --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/log.h @@ -0,0 +1,103 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ + +#include +#include +#include +#include + +#include "strconv.h" +#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_windows\\" }); + std::string reduceFilenamePath = filenameSplit.size() > 0 ? "flutter_inappwebview_windows\\" + filenameSplit.back() : filename; + debugMsg = reduceFilenamePath + "(" + std::to_string(line) + "): " + debugMsg; + } + if (isError) { + std::cerr << debugMsg << std::endl; + } + else { + std::cout << debugMsg << std::endl; + } + OutputDebugString(utf8_to_wide("\n" + debugMsg + "\n").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 std::wstring& msg, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { + debugLog(wide_to_utf8(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< + typename T, + typename = typename std::enable_if::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); + } + + static inline std::string getHRMessage(const HRESULT& error) + { + return wide_to_utf8(_com_error(error).ErrorMessage()); + } + + static inline void debugLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + auto isError = hr != S_OK; + auto errorCode = std::to_string(hr); + debugLog((isError ? "Error " + errorCode + ": " : "Message: ") + getHRMessage(hr), isError, filename, line); + } + + static inline bool succeededOrLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + if (SUCCEEDED(hr)) { + return true; + } + debugLog(hr, filename, line); + return false; + } + + static inline bool failedAndLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + if (FAILED(hr)) { + debugLog(hr, filename, line); + return true; + } + return false; + } + + static inline void failedLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + if (FAILED(hr)) { + debugLog(hr, filename, line); + } + } +} + +#ifndef NDEBUG +#define debugLog(value) debugLog(value, false, __FILE__, __LINE__) +#define succeededOrLog(value) succeededOrLog(value, __FILE__, __LINE__) +#define failedAndLog(value) failedAndLog(value, __FILE__, __LINE__) +#define failedLog(value) failedLog(value, __FILE__, __LINE__) +#endif + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/map.h b/flutter_inappwebview_windows/windows/utils/map.h new file mode 100644 index 000000000..c46d4a17f --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/map.h @@ -0,0 +1,37 @@ +#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()[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; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/strconv.h b/flutter_inappwebview_windows/windows/utils/strconv.h new file mode 100644 index 000000000..0f1483ff7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/strconv.h @@ -0,0 +1,558 @@ +// from https://github.com/javacommons/strconv + +/* strconv.h v1.8.10 */ +/* Last Modified: 2021/08/30 21:53 */ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019-2021 JavaCommons +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +----------------------------------------------------------------------------- + */ + +#ifndef STRCONV_H +#define STRCONV_H + +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ +#if __cplusplus >= 201103L && !defined(STRCONV_CPP98) + static inline std::wstring cp_to_wide(const std::string& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::wstring result(out_length, L'\0'); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0], out_length); + return result; + } + static inline std::string wide_to_cp(const std::wstring& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::string result(out_length, '\0'); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0], out_length, 0, 0); + return result; + } +#else /* __cplusplus < 201103L */ + static inline std::wstring cp_to_wide(const std::string& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::vector buffer(out_length); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &buffer[0], out_length); + std::wstring result(buffer.begin(), buffer.end()); + return result; + } + static inline std::string wide_to_cp(const std::wstring& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::vector buffer(out_length); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &buffer[0], out_length, 0, 0); + std::string result(buffer.begin(), buffer.end()); + return result; + } +#endif + + static inline std::string cp_to_utf8(const std::string& s, UINT codepage) + { + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, codepage); + return wide_to_cp(wide, CP_UTF8); + } + static inline std::string utf8_to_cp(const std::string& s, UINT codepage) + { + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, CP_UTF8); + return wide_to_cp(wide, codepage); + } + + static inline std::wstring ansi_to_wide(const std::string& s) + { + return cp_to_wide(s, CP_ACP); + } + static inline std::string wide_to_ansi(const std::wstring& s) + { + return wide_to_cp(s, CP_ACP); + } + + static inline std::wstring sjis_to_wide(const std::string& s) + { + return cp_to_wide(s, 932); + } + static inline std::string wide_to_sjis(const std::wstring& s) + { + return wide_to_cp(s, 932); + } + + static inline std::wstring utf8_to_wide(const std::string& s) + { + return cp_to_wide(s, CP_UTF8); + } + static inline std::string wide_to_utf8(const std::wstring& s) + { + return wide_to_cp(s, CP_UTF8); + } + + static inline std::string ansi_to_utf8(const std::string& s) + { + return cp_to_utf8(s, CP_ACP); + } + static inline std::string utf8_to_ansi(const std::string& s) + { + return utf8_to_cp(s, CP_ACP); + } + + static inline std::string sjis_to_utf8(const std::string& s) + { + return cp_to_utf8(s, 932); + } + static inline std::string utf8_to_sjis(const std::string& s) + { + return utf8_to_cp(s, 932); + } + +#ifdef __cpp_char8_t + static inline std::u8string utf8_to_char8(const std::string& s) + { + return std::u8string(s.begin(), s.end()); + } + static inline std::string char8_to_utf8(const std::u8string& s) + { + return std::string(s.begin(), s.end()); + } + + static inline std::wstring char8_to_wide(const std::u8string& s) + { + return cp_to_wide(char8_to_utf8(s), CP_UTF8); + } + static inline std::u8string wide_to_char8(const std::wstring& s) + { + return utf8_to_char8(wide_to_cp(s, CP_UTF8)); + } + + static inline std::u8string cp_to_char8(const std::string& s, UINT codepage) + { + return utf8_to_char8(cp_to_utf8(s, codepage)); + } + static inline std::string char8_to_cp(const std::u8string& s, UINT codepage) + { + return utf8_to_cp(char8_to_utf8(s), codepage); + } + + static inline std::u8string ansi_to_char8(const std::string& s) + { + return cp_to_char8(s, CP_ACP); + } + static inline std::string char8_to_ansi(const std::u8string& s) + { + return char8_to_cp(s, CP_ACP); + } + + static inline std::u8string sjis_to_char8(const std::string& s) + { + return cp_to_char8(s, 932); + } + static inline std::string char8_to_sjis(const std::u8string& s) + { + return char8_to_cp(s, 932); + } +#endif + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + + static inline std::wstring vformat(const wchar_t* format, va_list args) + { + int len = _vsnwprintf(0, 0, format, args); + if (len < 0) + return L""; + std::vector buffer(len + 1); + len = _vsnwprintf(&buffer[0], len, format, args); + if (len < 0) + return L""; + buffer[len] = L'\0'; + return &buffer[0]; + } + static inline std::string vformat(const char* format, va_list args) + { + int len = _vsnprintf(0, 0, format, args); + if (len < 0) + return ""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, format, args); + if (len < 0) + return ""; + buffer[len] = '\0'; + return &buffer[0]; + } +#ifdef __cpp_char8_t + static inline std::u8string vformat(const char8_t* format, va_list args) + { + int len = _vsnprintf(0, 0, (const char*)format, args); + if (len < 0) + return u8""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, (const char*)format, args); + if (len < 0) + return u8""; + buffer[len] = '\0'; + return (char8_t*)&buffer[0]; + } +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + static inline std::wstring format(const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return s; + } + static inline std::string format(const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return s; + } +#ifdef __cpp_char8_t + static inline std::u8string format(const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return s; + } +#endif + + static inline void format(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_utf8(s) << std::flush; + } + static inline void format(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << s << std::flush; + } +#ifdef __cpp_char8_t + static inline void format(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_utf8(s) << std::flush; + } +#endif + + static inline std::string formatA(const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return wide_to_ansi(s); + } + static inline std::string formatA(const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return utf8_to_ansi(s); + } +#ifdef __cpp_char8_t + static inline std::string formatA(const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return char8_to_ansi(s); + } +#endif + + static inline void formatA(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_ansi(s) << std::flush; + } + static inline void formatA(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << utf8_to_ansi(s) << std::flush; + } +#ifdef __cpp_char8_t + static inline void formatA(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_ansi(s) << std::flush; + } +#endif + + static inline void dbgmsg(const wchar_t* title, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + MessageBoxW(0, s.c_str(), title, MB_OK); + } + static inline void dbgmsg(const char* title, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + MessageBoxW(0, utf8_to_wide(s).c_str(), utf8_to_wide(title).c_str(), MB_OK); + } +#ifdef __cpp_char8_t + static inline void dbgmsg(const char8_t* title, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + MessageBoxW(0, char8_to_wide(s).c_str(), char8_to_wide(title).c_str(), MB_OK); + } +#endif + + static inline HANDLE handle_for_ostream(std::ostream& ostrm) + { + if (&ostrm == &std::cout) { + return GetStdHandle(STD_OUTPUT_HANDLE); + } + else if (&ostrm == &std::cerr) { + return GetStdHandle(STD_ERROR_HANDLE); + } + return INVALID_HANDLE_VALUE; + } + static inline void dbgout(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring ws = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + std::string s = wide_to_cp(ws, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else { + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } + static inline void dbgout(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + s = utf8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else { + std::wstring ws = utf8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } +#ifdef __cpp_char8_t + static inline void dbgout(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + std::string str = char8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, (const char*)str.c_str(), (DWORD)str.size(), &dwNumberOfCharsWrite, NULL); + } + else { + std::wstring ws = char8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } +#endif + + class unicode_ostream + { + private: + std::ostream* m_ostrm; + UINT m_target_cp; + bool is_ascii(const std::string& s) + { + for (std::size_t i = 0; i < s.size(); i++) { + unsigned char c = (unsigned char)s[i]; + if (c > 0x7f) + return false; + } + return true; + } + + public: + unicode_ostream(std::ostream& ostrm, UINT target_cp = CP_ACP) : m_ostrm(&ostrm), m_target_cp(target_cp) {} + std::ostream& stream() { return *m_ostrm; } + void stream(std::ostream& ostrm) { m_ostrm = &ostrm; } + UINT target_cp() { return m_target_cp; } + void target_cp(UINT cp) { m_target_cp = cp; } + template + unicode_ostream& operator<<(const T& x) + { + std::ostringstream oss; + oss << x; + std::string output = oss.str(); + if (is_ascii(output)) { + (*m_ostrm) << x; + } + else { + (*m_ostrm) << utf8_to_cp(output, m_target_cp); + } + return *this; + } + unicode_ostream& operator<<(const std::wstring& x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const wchar_t* x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const std::string& x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const char* x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } +#ifdef __cpp_char8_t + unicode_ostream& operator<<(const std::u8string& x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const char8_t* x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } +#endif + unicode_ostream& operator<<(std::ostream& (*pf)(std::ostream&)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } + unicode_ostream& operator<<(std::basic_ios& (*pf)(std::basic_ios&)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } + }; +} + +#define U8(X) ((const char *)u8##X) +#define WIDE(X) (L##X) + +#endif /* STRCONV_H */ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/string.h b/flutter_inappwebview_windows/windows/utils/string.h new file mode 100644 index 000000000..50e18db24 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/string.h @@ -0,0 +1,201 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ + +#include +#include +#include +#include +#include + +#include "strconv.h" + +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; + } + + static inline bool string_equals(const std::string& s1, const std::wstring& s2) + { + return string_equals(s1, wide_to_utf8(s2)); + } + + static inline bool string_equals(const std::wstring& s1, const std::string& s2) + { + return string_equals(wide_to_utf8(s1), s2); + } + + 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{ "" } : /* leave early if there are no items in the list */ + std::accumulate( /* otherwise, accumulate */ + ++vec.begin(), vec.end(), /* the range 2nd to after-last */ + *vec.begin(), /* and start accumulating with the first item */ + [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(const 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(const std::basic_string& s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](const T v) { return static_cast(std::toupper(v)); }); + return s2; + } + + 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; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/timer.h b/flutter_inappwebview_windows/windows/utils/timer.h new file mode 100644 index 000000000..198293384 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/timer.h @@ -0,0 +1,70 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ + +#include +#include +#include + +#include "map.h" + +namespace flutter_inappwebview_plugin +{ + class Timer { + public: + static UINT_PTR setTimeout(std::function callback, uint32_t delay) + { + auto timerId = SetTimer(NULL, 0, delay, (TIMERPROC)&Timer::timerCallback_); + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer#return-value + if (timerId != 0) { + timeoutCallbacks_[timerId] = callback; + } + return timerId; + } + + static UINT_PTR setInterval(std::function callback, uint32_t delay) + { + auto timerId = SetTimer(NULL, 0, delay, (TIMERPROC)&Timer::timerCallback_); + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer#return-value + if (timerId != 0) { + intervalCallbacks_[timerId] = callback; + } + return timerId; + } + + static bool clearTimeout(UINT_PTR timerId) + { + if (map_contains(timeoutCallbacks_, timerId)) { + timeoutCallbacks_.erase(timerId); + return (bool)KillTimer(NULL, timerId); + } + return false; + } + + static bool clearInterval(UINT_PTR timerId) + { + if (map_contains(intervalCallbacks_, timerId)) { + intervalCallbacks_.erase(timerId); + return (bool)KillTimer(NULL, timerId); + } + return false; + } + private: + static inline std::map> timeoutCallbacks_ = {}; + static inline std::map> intervalCallbacks_ = {}; + static void CALLBACK timerCallback_(HWND hwnd, UINT uMsg, UINT_PTR timerId, DWORD dwTime) + { + if (map_contains(timeoutCallbacks_, timerId)) { + timeoutCallbacks_.at(timerId)(); + clearTimeout(timerId); + } + else if (map_contains(intervalCallbacks_, timerId)) { + intervalCallbacks_.at(timerId)(); + } + else { + KillTimer(NULL, timerId); + } + } + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/uri.h b/flutter_inappwebview_windows/windows/utils/uri.h new file mode 100644 index 000000000..6705a358a --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/uri.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ + +#include +#include + +#include "string.h" + +namespace flutter_inappwebview_plugin { + static inline std::string get_origin_from_url(const std::string &url) { + try { + winrt::Windows::Foundation::Uri const uri{utf8_to_wide(url)}; + auto scheme = winrt::to_string(uri.SchemeName()); + auto host = winrt::to_string(uri.Host()); + if (!scheme.empty() && !host.empty()) { + auto uriPort = uri.Port(); + std::string port = ""; + if (uriPort > 0 && ((string_equals(scheme, "http") && uriPort != 80) || + (string_equals(scheme, "https") && uriPort != 443))) { + port = ":" + std::to_string(uriPort); + } + return scheme + "://" + host + port; + } + } + catch (...) {} + 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 host = afterSchemeSplit[0]; + return scheme + "://" + host; + } + + return url; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/util.h b/flutter_inappwebview_windows/windows/utils/util.h new file mode 100644 index 000000000..99e6d9960 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/util.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + 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); + } + + static inline float get_current_scale_factor(HWND hwnd) + { + auto dpi = GetDpiForWindow(hwnd); + return dpi > 0 ? dpi / 96.0f : 1.0f; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/uuid.h b/flutter_inappwebview_windows/windows/utils/uuid.h new file mode 100644 index 000000000..cf7396621 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/uuid.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ + +#include +#include + +namespace flutter_inappwebview_plugin { + static inline std::string get_uuid() + { + UUID uuid = { 0 }; + std::string guid; + + ::UuidCreate(&uuid); + + RPC_CSTR szUuid = NULL; + if (::UuidToStringA(&uuid, &szUuid) == RPC_S_OK) { + guid = (char*)szUuid; + ::RpcStringFreeA(&szUuid); + } + + return guid; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/vector.h b/flutter_inappwebview_windows/windows/utils/vector.h new file mode 100644 index 000000000..4a70e8a41 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/vector.h @@ -0,0 +1,99 @@ +#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::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) + { + std::remove(vec.begin(), vec.end(), el); + } + + template + static inline void vector_remove_if(std::vector& vec, UnaryPredicate&& predicate) + { + std::remove_if(vec.begin(), vec.end(), std::forward(predicate)); + } + + 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)); + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp new file mode 100644 index 000000000..e8c5669b8 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp @@ -0,0 +1,325 @@ +#include +#include + +#include "../utils/log.h" +#include "webview_environment.h" + +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + WebViewEnvironment::WebViewEnvironment(const FlutterInappwebviewWindowsPlugin* plugin, const std::string& id) + : plugin(plugin), id(id), + channelDelegate(std::make_unique(this, plugin->registrar->messenger())) + {} + + void WebViewEnvironment::create(const std::unique_ptr settings, const std::function completionHandler) + { + if (!plugin) { + if (completionHandler) { + completionHandler(E_FAIL); + } + return; + } + + auto hwnd = plugin->webViewEnvironmentManager->getHWND(); + if (!hwnd) { + if (completionHandler) { + completionHandler(E_FAIL); + } + return; + } + + auto options = Make(); + if (settings) { + if (settings->additionalBrowserArguments.has_value()) { + options->put_AdditionalBrowserArguments(utf8_to_wide(settings->additionalBrowserArguments.value()).c_str()); + } + if (settings->allowSingleSignOnUsingOSPrimaryAccount.has_value()) { + options->put_AllowSingleSignOnUsingOSPrimaryAccount(settings->allowSingleSignOnUsingOSPrimaryAccount.value()); + } + if (settings->language.has_value()) { + options->put_Language(utf8_to_wide(settings->language.value()).c_str()); + } + if (settings->targetCompatibleBrowserVersion.has_value()) { + options->put_TargetCompatibleBrowserVersion(utf8_to_wide(settings->targetCompatibleBrowserVersion.value()).c_str()); + } + wil::com_ptr options2; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options2))) && settings->exclusiveUserDataFolderAccess.has_value()) { + options2->put_ExclusiveUserDataFolderAccess(settings->exclusiveUserDataFolderAccess.value()); + } + wil::com_ptr options3; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options3))) && settings->isCustomCrashReportingEnabled.has_value()) { + options3->put_IsCustomCrashReportingEnabled(settings->isCustomCrashReportingEnabled.value()); + } + wil::com_ptr options4; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options4))) && settings->customSchemeRegistrations.has_value()) { + std::vector registrations = {}; + for (auto& customSchemeRegistration : settings->customSchemeRegistrations.value()) { + registrations.push_back(std::move(customSchemeRegistration->toWebView2CustomSchemeRegistration())); + } + options4->SetCustomSchemeRegistrations(static_cast(registrations.size()), registrations.data()); + } + wil::com_ptr options5; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options5))) && settings->enableTrackingPrevention.has_value()) { + options5->put_EnableTrackingPrevention(settings->enableTrackingPrevention.value()); + } + wil::com_ptr options6; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options6))) && settings->areBrowserExtensionsEnabled.has_value()) { + options6->put_AreBrowserExtensionsEnabled(settings->areBrowserExtensionsEnabled.value()); + } + wil::com_ptr options7; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options7)))) { + if (settings->channelSearchKind.has_value()) { + options7->put_ChannelSearchKind(static_cast(settings->channelSearchKind.value())); + } + if (settings->releaseChannels.has_value()) { + options7->put_ReleaseChannels(static_cast(settings->releaseChannels.value())); + } + } + wil::com_ptr options8; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options8))) && settings->scrollbarStyle.has_value()) { + options8->put_ScrollBarStyle(static_cast(settings->scrollbarStyle.value())); + } + } + + auto hr = CreateCoreWebView2EnvironmentWithOptions( + settings && settings->browserExecutableFolder.has_value() ? utf8_to_wide(settings->browserExecutableFolder.value()).c_str() : nullptr, + settings && settings->userDataFolder.has_value() ? utf8_to_wide(settings->userDataFolder.value()).c_str() : nullptr, + options.Get(), + Callback( + [this, hwnd, completionHandler](HRESULT result, wil::com_ptr environment) -> HRESULT + { + if (succeededOrLog(result)) { + environment_ = std::move(environment); + + auto add_NewBrowserVersionAvailable_HResult = environment_->add_NewBrowserVersionAvailable(Callback( + [this](ICoreWebView2Environment* sender, IUnknown* args) + { + if (channelDelegate) { + channelDelegate->onNewBrowserVersionAvailable(); + } + return S_OK; + } + ).Get(), &newBrowserVersionAvailableToken_); + failedLog(add_NewBrowserVersionAvailable_HResult); + + if (auto environment5 = environment_.try_query()) { + auto add_BrowserProcessExited_HResult = environment5->add_BrowserProcessExited(Callback( + [this](ICoreWebView2Environment* sender, ICoreWebView2BrowserProcessExitedEventArgs* args) + { + if (channelDelegate) { + COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND exitKind; + std::optional kind = SUCCEEDED(args->get_BrowserProcessExitKind(&exitKind)) ? static_cast(exitKind) : std::optional{}; + + UINT32 pid; + std::optional processId = SUCCEEDED(args->get_BrowserProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + auto browserProcessExitedDetail = std::make_shared(kind, processId); + channelDelegate->onBrowserProcessExited(std::move(browserProcessExitedDetail)); + } + return S_OK; + } + ).Get(), &browserProcessExitedToken_); + failedLog(add_BrowserProcessExited_HResult); + } + + if (auto environment8 = environment_.try_query()) { + auto add_ProcessInfosChanged_HResult = environment8->add_ProcessInfosChanged(Callback( + [this, environment8](ICoreWebView2Environment* sender, IUnknown* args) + { + if (!environment_) { + return S_OK; + } + if (auto environment13 = environment_.try_query()) { + auto hr = environment13->GetProcessExtendedInfos(Callback( + [this](HRESULT error, wil::com_ptr processCollection) -> HRESULT + { + if (succeededOrLog(error) && processCollection) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(processCollection); + channelDelegate->onProcessInfosChanged(std::move(browserProcessInfosChangedDetail)); + } + return S_OK; + }).Get()); + + if (succeededOrLog(hr)) { + return S_OK; + } + } + wil::com_ptr processCollection; + if (channelDelegate && succeededOrLog(environment8->GetProcessInfos(&processCollection))) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(processCollection); + channelDelegate->onProcessInfosChanged(std::move(browserProcessInfosChangedDetail)); + } + return S_OK; + } + ).Get(), &processInfosChangedToken_); + failedLog(add_ProcessInfosChanged_HResult); + } + + completionHandler(S_OK); + } + else if (completionHandler) { + completionHandler(result); + } + return S_OK; + }).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(hr); + } + } + + void WebViewEnvironment::useTempWebView(const std::function, wil::com_ptr)> completionHandler) const + { + auto hwnd = plugin->webViewEnvironmentManager->getHWND(); + if (!hwnd) { + if (completionHandler) { + completionHandler(nullptr, nullptr); + } + return; + } + + auto hr = environment_->CreateCoreWebView2Controller(hwnd, Callback( + [this, completionHandler](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (succeededOrLog(result)) { + controller->put_IsVisible(false); + + wil::com_ptr webView_; + controller->get_CoreWebView2(&webView_); + + if (completionHandler) { + completionHandler(std::move(controller), std::move(webView_)); + } + } + else if (completionHandler) { + completionHandler(nullptr, nullptr); + } + return S_OK; + }).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(nullptr, nullptr); + } + } + + bool WebViewEnvironment::isInterfaceSupported(const std::string& interfaceName) const + { + if (!environment_) { + return false; + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Environment" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Environment"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment2"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment3"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment4"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment5"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment6"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment7"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment8"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment9"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment10"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment11"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment12"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment13"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment14"): + return environment_.try_query() != nullptr; + default: + return false; + } + } + + return false; + } + + void WebViewEnvironment::getProcessInfos(const std::function>)> completionHandler) const + { + if (!environment_) { + if (completionHandler) { + completionHandler({}); + } + return; + } + + if (auto environment13 = environment_.try_query()) { + auto hr = environment13->GetProcessExtendedInfos(Callback( + [completionHandler](HRESULT error, wil::com_ptr processCollection) -> HRESULT + { + std::vector> processInfos = {}; + if (succeededOrLog(error) && processCollection) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(processCollection); + processInfos = browserProcessInfosChangedDetail->infos; + } + if (completionHandler) { + completionHandler(processInfos); + } + return S_OK; + }).Get()); + + if (succeededOrLog(hr)) { + return; + } + } + std::vector> processInfos = {}; + if (auto environment8 = environment_.try_query()) { + wil::com_ptr processCollection; + if (succeededOrLog(environment8->GetProcessInfos(&processCollection))) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(processCollection); + processInfos = browserProcessInfosChangedDetail->infos; + } + } + + if (completionHandler) { + completionHandler(processInfos); + } + } + + std::optional WebViewEnvironment::getFailureReportFolderPath() const + { + if (!environment_) { + return std::optional{}; + } + + if (auto environment11 = environment_.try_query()) { + wil::unique_cotaskmem_string failureReportFolderPath; + if (succeededOrLog(environment11->get_FailureReportFolderPath(&failureReportFolderPath))) { + return wide_to_utf8(failureReportFolderPath.get()); + } + } + + return std::optional{}; + } + + WebViewEnvironment::~WebViewEnvironment() + { + debugLog("dealloc WebViewEnvironment"); + if (environment_) { + environment_->remove_NewBrowserVersionAvailable(newBrowserVersionAvailableToken_); + if (auto environment5 = environment_.try_query()) { + environment5->remove_BrowserProcessExited(browserProcessExitedToken_); + } + if (auto environment8 = environment_.try_query()) { + environment8->remove_ProcessInfosChanged(processInfosChangedToken_); + } + } + environment_ = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h new file mode 100644 index 000000000..7ac937993 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h @@ -0,0 +1,49 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ + +#include +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "webview_environment_channel_delegate.h" +#include "webview_environment_settings.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironment + { + public: + static inline const wchar_t* CLASS_NAME = L"WebViewEnvironment"; + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_webview_environment_"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::string id; + + std::unique_ptr channelDelegate; + + WebViewEnvironment(const FlutterInappwebviewWindowsPlugin* plugin, const std::string& id); + ~WebViewEnvironment(); + + void create(const std::unique_ptr settings, const std::function completionHandler); + wil::com_ptr getEnvironment() + { + return environment_; + } + // without using a "temp" ICoreWebView2 for CookieManager and other possible usage, the onBrowserProcessExited event will never be called + void useTempWebView(const std::function, wil::com_ptr)> completionHandler) const; + bool isInterfaceSupported(const std::string& interfaceName) const; + void getProcessInfos(const std::function>)> completionHandler) const; + std::optional getFailureReportFolderPath() const; + + private: + wil::com_ptr environment_; + EventRegistrationToken processInfosChangedToken_ = { 0 }; + EventRegistrationToken browserProcessExitedToken_ = { 0 }; + EventRegistrationToken newBrowserVersionAvailableToken_ = { 0 }; + WNDCLASS windowClass_ = {}; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp new file mode 100644 index 000000000..d8546427d --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp @@ -0,0 +1,90 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "webview_environment.h" +#include "webview_environment_channel_delegate.h" + +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentChannelDelegate::WebViewEnvironmentChannelDelegate(WebViewEnvironment* webViewEnv, flutter::BinaryMessenger* messenger) + : webViewEnvironment(webViewEnv), ChannelDelegate(messenger, WebViewEnvironment::METHOD_CHANNEL_NAME_PREFIX + webViewEnv->id) + {} + + void WebViewEnvironmentChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + if (!webViewEnvironment) { + result->Success(); + return; + } + + auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "dispose")) { + if (webViewEnvironment->plugin && webViewEnvironment->plugin->webViewEnvironmentManager) { + std::map>& webViewEnvironments = webViewEnvironment->plugin->webViewEnvironmentManager->webViewEnvironments; + auto& id = webViewEnvironment->id; + if (map_contains(webViewEnvironments, id)) { + webViewEnvironments.erase(id); + } + } + result->Success(); + } + else if (string_equals(methodName, "isInterfaceSupported")) { + auto interfaceName = get_fl_map_value(arguments, "interface"); + result->Success(webViewEnvironment->isInterfaceSupported(interfaceName)); + } + else if (string_equals(methodName, "getProcessInfos")) { + auto result_ = std::shared_ptr>(std::move(result)); + webViewEnvironment->getProcessInfos([result_ = std::move(result_)](std::vector> processInfos) + { + result_->Success(make_fl_value(functional_map(processInfos, [](const std::shared_ptr& info) { return info->toEncodableMap(); }))); + }); + } + else if (string_equals(methodName, "getFailureReportFolderPath")) { + result->Success(make_fl_value(webViewEnvironment->getFailureReportFolderPath())); + } + else { + result->NotImplemented(); + } + } + + void WebViewEnvironmentChannelDelegate::onNewBrowserVersionAvailable() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onNewBrowserVersionAvailable", nullptr); + } + + void WebViewEnvironmentChannelDelegate::onBrowserProcessExited(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onBrowserProcessExited", std::move(arguments)); + } + + void WebViewEnvironmentChannelDelegate::onProcessInfosChanged(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onProcessInfosChanged", std::move(arguments)); + } + + WebViewEnvironmentChannelDelegate::~WebViewEnvironmentChannelDelegate() + { + debugLog("dealloc WebViewEnvironmentChannelDelegate"); + webViewEnvironment = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h new file mode 100644 index 000000000..c51c18f05 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ + +#include + +#include "../types/browser_process_exited_detail.h" +#include "../types/browser_process_infos_changed_detail.h" +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironment; + + class WebViewEnvironmentChannelDelegate : public ChannelDelegate + { + public: + WebViewEnvironment* webViewEnvironment; + + WebViewEnvironmentChannelDelegate(WebViewEnvironment* webViewEnv, flutter::BinaryMessenger* messenger); + ~WebViewEnvironmentChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onNewBrowserVersionAvailable() const; + void onBrowserProcessExited(std::shared_ptr detail) const; + void onProcessInfosChanged(std::shared_ptr detail) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp new file mode 100644 index 000000000..bae69c1a5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentManager::WebViewEnvironmentManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), WebViewEnvironmentManager::METHOD_CHANNEL_NAME) + { + windowClass_.lpszClassName = WebViewEnvironmentManager::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + + hwnd_ = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0, + 0, 0, 0, + plugin->registrar->GetView()->GetNativeWindow(), + nullptr, + windowClass_.hInstance, nullptr); + } + + void WebViewEnvironmentManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "create")) { + auto id = get_fl_map_value(*arguments, "id"); + auto settingsMap = get_optional_fl_map_value(*arguments, "settings"); + auto settings = settingsMap.has_value() ? std::make_unique(settingsMap.value()) : nullptr; + createWebViewEnvironment(id, std::move(settings), std::move(result)); + } + else if (string_equals(methodName, "getAvailableVersion")) { + auto browserExecutableFolder = get_optional_fl_map_value(*arguments, "browserExecutableFolder"); + result->Success(make_fl_value(getAvailableVersion(browserExecutableFolder))); + } + else if (string_equals(methodName, "compareBrowserVersions")) { + auto version1 = get_fl_map_value(*arguments, "version1"); + auto version2 = get_fl_map_value(*arguments, "version2"); + result->Success(make_fl_value(compareBrowserVersions(version1, version2))); + } + else { + result->NotImplemented(); + } + } + + void WebViewEnvironmentManager::createWebViewEnvironment(const std::string& id, std::unique_ptr settings, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + auto webViewEnvironment = std::make_unique(plugin, id); + webViewEnvironment->create(std::move(settings), + [this, id, result_](HRESULT errorCode) + { + if (succeededOrLog(errorCode)) { + result_->Success(true); + } + else { + result_->Error("0", "Cannot create WebViewEnvironment: " + getHRMessage(errorCode)); + if (map_contains(webViewEnvironments, id)) { + webViewEnvironments.erase(id); + } + } + }); + webViewEnvironments.insert({ id, std::move(webViewEnvironment) }); + } + + void WebViewEnvironmentManager::createOrGetDefaultWebViewEnvironment(const std::function completionHandler) + { + if (defaultEnvironment_) { + if (completionHandler) { + completionHandler(defaultEnvironment_.get()); + } + return; + } + + defaultEnvironment_ = std::make_unique(plugin, "-1"); + defaultEnvironment_->create(nullptr, [this, completionHandler](HRESULT errorCode) + { + if (succeededOrLog(errorCode)) { + if (completionHandler) { + completionHandler(defaultEnvironment_.get()); + } + } + else if (completionHandler) { + defaultEnvironment_ = nullptr; + completionHandler(nullptr); + } + }); + } + + std::optional WebViewEnvironmentManager::getAvailableVersion(std::optional browserExecutableFolder) + { + wil::unique_cotaskmem_string versionInfo; + if (succeededOrLog(GetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder.has_value() ? utf8_to_wide(browserExecutableFolder.value()).c_str() : nullptr, &versionInfo))) { + return wide_to_utf8(versionInfo.get()); + } + return std::nullopt; + } + + std::optional WebViewEnvironmentManager::compareBrowserVersions(std::string version1, std::string version2) + { + int result = 0; + if (succeededOrLog(CompareBrowserVersions(utf8_to_wide(version1).c_str(), utf8_to_wide(version2).c_str(), &result))) { + return result; + } + return std::nullopt; + } + + WebViewEnvironmentManager::~WebViewEnvironmentManager() + { + debugLog("dealloc WebViewEnvironmentManager"); + webViewEnvironments.clear(); + plugin = nullptr; + defaultEnvironment_ = nullptr; + if (hwnd_) { + DestroyWindow(hwnd_); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h new file mode 100644 index 000000000..6bfcafcba --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ + +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "webview_environment.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentManager : public ChannelDelegate + { + public: + static inline const wchar_t* CLASS_NAME = L"WebViewEnvironmentManager"; + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webview_environment"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViewEnvironments; + + WebViewEnvironmentManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~WebViewEnvironmentManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createWebViewEnvironment(const std::string& id, std::unique_ptr settings, std::unique_ptr> result); + void createOrGetDefaultWebViewEnvironment(const std::function completionHandler); + HWND getHWND() + { + return hwnd_; + } + + static std::optional getAvailableVersion(std::optional browserExecutableFolder); + static std::optional compareBrowserVersions(std::string version1, std::string version2); + private: + std::unique_ptr defaultEnvironment_; + WNDCLASS windowClass_ = {}; + HWND hwnd_ = nullptr; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp new file mode 100644 index 000000000..95802937a --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp @@ -0,0 +1,43 @@ +#include "../utils/flutter.h" +#include "webview_environment_settings.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentSettings::WebViewEnvironmentSettings(const flutter::EncodableMap& map) + : browserExecutableFolder(get_optional_fl_map_value(map, "browserExecutableFolder")), + userDataFolder(get_optional_fl_map_value(map, "userDataFolder")), + additionalBrowserArguments(get_optional_fl_map_value(map, "additionalBrowserArguments")), + allowSingleSignOnUsingOSPrimaryAccount(get_optional_fl_map_value(map, "allowSingleSignOnUsingOSPrimaryAccount")), + language(get_optional_fl_map_value(map, "language")), + targetCompatibleBrowserVersion(get_optional_fl_map_value(map, "targetCompatibleBrowserVersion")), + customSchemeRegistrations(functional_map(get_optional_fl_map_value(map, "customSchemeRegistrations"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })), + exclusiveUserDataFolderAccess(get_optional_fl_map_value(map, "exclusiveUserDataFolderAccess")), + isCustomCrashReportingEnabled(get_optional_fl_map_value(map, "isCustomCrashReportingEnabled")), + enableTrackingPrevention(get_optional_fl_map_value(map, "enableTrackingPrevention")), + areBrowserExtensionsEnabled(get_optional_fl_map_value(map, "areBrowserExtensionsEnabled")), + channelSearchKind(get_optional_fl_map_value(map, "channelSearchKind")), + releaseChannels(get_optional_fl_map_value(map, "releaseChannels")), + scrollbarStyle(get_optional_fl_map_value(map, "scrollbarStyle")) + {} + + flutter::EncodableMap WebViewEnvironmentSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"browserExecutableFolder", make_fl_value(browserExecutableFolder)}, + {"userDataFolder", make_fl_value(userDataFolder)}, + {"additionalBrowserArguments", make_fl_value(additionalBrowserArguments)}, + {"allowSingleSignOnUsingOSPrimaryAccount", make_fl_value(allowSingleSignOnUsingOSPrimaryAccount)}, + {"language", make_fl_value(language)}, + {"targetCompatibleBrowserVersion", make_fl_value(targetCompatibleBrowserVersion)}, + {"customSchemeRegistrations", make_fl_value(functional_map(customSchemeRegistrations, [](const std::shared_ptr& item) { return item->toEncodableMap(); }))}, + {"exclusiveUserDataFolderAccess", make_fl_value(exclusiveUserDataFolderAccess)}, + {"isCustomCrashReportingEnabled", make_fl_value(isCustomCrashReportingEnabled)}, + {"enableTrackingPrevention", make_fl_value(enableTrackingPrevention)}, + {"areBrowserExtensionsEnabled", make_fl_value(areBrowserExtensionsEnabled)}, + {"channelSearchKind", make_fl_value(channelSearchKind)}, + {"releaseChannels", make_fl_value(releaseChannels)}, + {"scrollbarStyle", make_fl_value(scrollbarStyle)} + }; + } + +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h new file mode 100644 index 000000000..bef41b343 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ + +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/custom_scheme_registration.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentSettings + { + public: + const std::optional browserExecutableFolder; + const std::optional userDataFolder; + const std::optional additionalBrowserArguments; + const std::optional allowSingleSignOnUsingOSPrimaryAccount; + const std::optional language; + const std::optional targetCompatibleBrowserVersion; + const std::optional>> customSchemeRegistrations; + const std::optional exclusiveUserDataFolderAccess; + const std::optional isCustomCrashReportingEnabled; + const std::optional enableTrackingPrevention; + const std::optional areBrowserExtensionsEnabled; + const std::optional channelSearchKind; + const std::optional releaseChannels; + const std::optional scrollbarStyle; + + WebViewEnvironmentSettings() = default; + WebViewEnvironmentSettings(const flutter::EncodableMap& map); + ~WebViewEnvironmentSettings() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ \ No newline at end of file diff --git a/package.json b/package.json index 1b18551af..c41fa8b95 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,36 @@ { "name": "flutter_inappwebview", "version": "1.0.0", - "directories": { - "example": "flutter_inappwebview/example", - "lib": "flutter_inappwebview/lib" - }, "private": true, "scripts": { - "build": "flutter pub run build_runner build --delete-conflicting-outputs", - "watch": "flutter pub run build_runner watch --delete-conflicting-outputs", - "publish:dry": "flutter pub publish --dry-run", - "publish": "flutter pub publish", - "format": "dart format flutter_inappwebview/lib flutter_inappwebview/example/integration_test flutter_inappwebview_platform_interface/lib flutter_inappwebview_android/lib flutter_inappwebview_ios/lib flutter_inappwebview_macos/lib flutter_inappwebview_web/lib", - "build:publish": "npm run format && npm run build && flutter pub publish", - "docs:gen": "flutter pub global activate dartdoc && flutter pub global run dartdoc:dartdoc", - "docs:serve": "flutter pub global activate dhttpd && flutter pub global run dhttpd:dhttpd --path doc/api" + "build": "cd flutter_inappwebview_platform_interface && ../.fvm/flutter_sdk/bin/flutter pub run build_runner build --delete-conflicting-outputs", + "watch": "cd flutter_inappwebview_platform_interface && ../.fvm/flutter_sdk/bin/flutter pub run build_runner watch --delete-conflicting-outputs", + + "publish:dry": ".fvm/flutter_sdk/bin/flutter pub publish --dry-run", + "format": ".fvm/flutter_sdk/bin/dart format flutter_inappwebview/lib flutter_inappwebview/example/integration_test flutter_inappwebview_platform_interface/lib flutter_inappwebview_android/lib flutter_inappwebview_ios/lib flutter_inappwebview_macos/lib flutter_inappwebview_web/lib flutter_inappwebview_windows/lib", + + "docs:gen": "cd flutter_inappwebview && ../.fvm/flutter_sdk/bin/dart doc ../", + "docs:serve": ".fvm/flutter_sdk/bin/flutter pub global activate dhttpd && .fvm/flutter_sdk/bin/flutter pub global run dhttpd:dhttpd --path doc/api", + + "publish:platform_interface": "cd flutter_inappwebview_platform_interface && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:android": "cd flutter_inappwebview_android && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:ios": "cd flutter_inappwebview_ios && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:macos": "cd flutter_inappwebview_macos && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:web": "cd flutter_inappwebview_web && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:windows": "cd flutter_inappwebview_windows && ../.fvm/flutter_sdk/bin/flutter pub publish && cd ..", + "publish:all_platforms": "npm run publish:android && npm run publish:ios && npm run publish:macos && npm run publish:web && npm run publish:windows", + "publish:interface_and_all_platforms": "npm run publish:platform_interface && npm run publish:all_platforms", + + "publish:plugin": "cd flutter_inappwebview && ../.fvm/flutter_sdk/bin/flutter pub publish && cd .." }, "repository": { "type": "git", "url": "git+https://github.com/pichillilorenzo/flutter_inappwebview.git" }, - "author": "", + "author": "Lorenzo Pichilli", "license": "ISC", "bugs": { "url": "https://github.com/pichillilorenzo/flutter_inappwebview/issues" }, - "homepage": "https://github.com/pichillilorenzo/flutter_inappwebview#readme" + "homepage": "https://github.com/pichillilorenzo/flutter_inappwebview#README.md" } diff --git a/test_node_server/index.js b/test_node_server/index.js index ad68fb3a7..72c0a8248 100755 --- a/test_node_server/index.js +++ b/test_node_server/index.js @@ -12,6 +12,7 @@ // - Overwrite certificate.pfx to example/test_assets/certificate.pfx const express = require('express'); const http = require('http'); +const net = require('net'); const https = require('https'); const cors = require('cors'); const auth = require('basic-auth'); @@ -224,19 +225,41 @@ app.get("/test-download-file", (req, res) => { app.listen(8082) // Proxy server -http.createServer(function (req, res) { - res.setHeader('Content-type', 'text/html'); - res.write(` - - - - -

Proxy Works

-

${req.url}

-

${req.method}

-

${JSON.stringify(req.headers)}

- - - `); - res.end(); -}).listen(8083); \ No newline at end of file +// Create an HTTP tunneling proxy +const proxy = http.createServer((req, res) => { + console.log('proxy response', req.url); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(` + + + + +

Proxy Works

+

${req.url}

+

${req.method}

+

${JSON.stringify(req.headers)}

+ + + `); +}); +proxy.on('connect', (req, clientSocket, head) => { + console.log('proxy connect request'); + // Connect to an origin server + // const { port, hostname } = new URL(`http://${req.url}`); + const { port, hostname } = new URL(`http://127.0.0.1:8083`); + const serverSocket = net.connect(port || 80, hostname, () => { + clientSocket.write('HTTP/1.1 200 Connection Established\r\n' + + 'Proxy-agent: Node.js-Proxy\r\n' + + '\r\n'); + serverSocket.write(head); + serverSocket.pipe(clientSocket); + clientSocket.pipe(serverSocket); + }); +}); +proxy.listen(8083, null, () => { + console.log('proxy server listening on port 8083'); +}); + +process.on('uncaughtException', function (err) { + console.error(err); +}); \ No newline at end of file diff --git a/test_node_server/package-lock.json b/test_node_server/package-lock.json old mode 100755 new mode 100644 index feab47d69..b8e76f968 --- a/test_node_server/package-lock.json +++ b/test_node_server/package-lock.json @@ -9,11 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "basic-auth": "latest", - "body-parser": "^1.20.0", + "basic-auth": "*", + "body-parser": "^1.20.3", "cors": "^2.8.5", "express": "latest", - "https": "latest", + "https": "*", "multiparty": "^4.2.3" } }, @@ -65,20 +65,20 @@ } }, "node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.13.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -95,13 +95,28 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -138,17 +153,17 @@ ] }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -195,63 +210,103 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -260,104 +315,10 @@ }, "engines": { "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/safe-buffer": { @@ -379,34 +340,13 @@ } ] }, - "node_modules/express/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -417,25 +357,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -447,44 +368,69 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { - "function-bind": "^1.1.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -492,6 +438,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -536,6 +493,14 @@ "node": ">= 0.10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -545,9 +510,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -672,9 +640,12 @@ } }, "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -699,9 +670,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -716,11 +687,11 @@ } }, "node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -746,9 +717,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -770,9 +741,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -792,98 +763,101 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } }, - "node_modules/send/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "ee-first": "1.1.1" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/setprototypeof": { + "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/send/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">=0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -992,20 +966,20 @@ } }, "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.13.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } @@ -1015,13 +989,22 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" } }, "content-disposition": { @@ -1040,14 +1023,14 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -1081,57 +1064,85 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "requires": { + "es-errors": "^1.3.0" + } }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1139,128 +1150,25 @@ "vary": "~1.1.2" }, "dependencies": { - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" - }, - "dependencies": { - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } } }, "forwarded": { @@ -1271,35 +1179,56 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "requires": { - "function-bind": "^1.1.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } }, "http-errors": { "version": "2.0.0", @@ -1336,15 +1265,20 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "methods": { "version": "1.1.2", @@ -1424,9 +1358,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" }, "on-finished": { "version": "2.4.1", @@ -1442,9 +1376,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "proxy-addr": { "version": "2.0.7", @@ -1456,11 +1390,11 @@ } }, "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "random-bytes": { @@ -1474,9 +1408,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1495,9 +1429,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -1514,67 +1448,27 @@ "statuses": "2.0.1" }, "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "setprototypeof": { @@ -1583,13 +1477,47 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "statuses": { diff --git a/test_node_server/package.json b/test_node_server/package.json old mode 100755 new mode 100644 index ef4059369..f3b8e9ff5 --- a/test_node_server/package.json +++ b/test_node_server/package.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "basic-auth": "latest", - "body-parser": "^1.20.0", + "body-parser": "^1.20.3", "cors": "^2.8.5", "express": "latest", "https": "latest",