├── Runtime
├── Native
│ ├── Android
│ │ ├── WebViewPlugin.aar
│ │ └── WebViewPlugin.aar.meta
│ ├── iOS.meta
│ ├── Android.meta
│ ├── IOSWebView.cs.meta
│ ├── WebViewBase.cs.meta
│ ├── AndroidWebView.cs.meta
│ ├── iOS
│ │ ├── WebView.mm.meta
│ │ ├── WebViewWithUIWebView.mm.meta
│ │ ├── WebView.mm
│ │ └── WebViewWithUIWebView.mm
│ ├── WebViewBase.cs
│ ├── AndroidWebView.cs
│ └── IOSWebView.cs
├── Data.meta
├── Native.meta
├── Data
│ ├── Enums.cs.meta
│ ├── WebViewOptions.cs.meta
│ ├── WebViewOptions.cs
│ └── Enums.cs
├── WebViewPanel.cs.meta
└── WebViewPanel.cs
├── .github
├── latest.md
├── CODEOWNERS
├── workflows
│ ├── auto-release-action.yml
│ └── pr-test-runner.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── pull_request_template.md
├── LICENSE.md.meta
├── CHANGELOG.md.meta
├── README.md.meta
├── SUPPORT.md.meta
├── CONTRIBUTING.md.meta
├── HOW_TO_INSTALL.md.meta
├── package.json.meta
├── CODE_OF_CONDUCT.md.meta
├── Third Party Notices.md.meta
├── Editor.meta
├── Resources.meta
├── Resources
├── WebView Canvas.prefab.meta
└── WebView Canvas.prefab
├── Runtime.meta
├── Samples~
├── WebView
│ └── WebView.unity.meta
└── WebView.meta
├── ReadyPlayerMe.WebView.asmdef.meta
├── Editor
├── ReadyPlayerMe.WebView.Editor.asmdef.meta
├── WebViewEditor.cs.meta
├── IOSBuildProcessor.cs.meta
├── AndroidBuildProcessor.cs.meta
├── ReadyPlayerMe.WebView.Editor.asmdef
├── WebViewEditor.cs
├── IOSBuildProcessor.cs
└── AndroidBuildProcessor.cs
├── SUPPORT.md
├── ReadyPlayerMe.WebView.asmdef
├── .githooks
├── commit-msg
└── pre-commit
├── LICENSE.md
├── Third Party Notices.md
├── package.json
├── .gitignore
├── HOW_TO_INSTALL.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── README.md
└── CONTRIBUTING.md
/Runtime/Native/Android/WebViewPlugin.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/readyplayerme/rpm-unity-sdk-webview/HEAD/Runtime/Native/Android/WebViewPlugin.aar
--------------------------------------------------------------------------------
/.github/latest.md:
--------------------------------------------------------------------------------
1 |
2 | ## Changelog
3 |
4 | ### Fixed
5 | - update IOS build processor to include WebKit framework in [#37](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/37)
6 |
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 65406795b8466614aa88ca47cade5dcd
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ad275b9bd486c45f9b514ecdf0e65aca
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fe6d42c25ea624672a00bf6406293f7d
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/SUPPORT.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1af6249ec64314cb7a9e7e21360a4318
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 437a8dcb576155e4eb49139c831d4b74
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/HOW_TO_INSTALL.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c54388e703da4794dadeaa4a744ba971
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 35b1168cbade94739a5ca52c936615cb
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 899e02fa2cf38aa479487597d6ff38df
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Third Party Notices.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5df6823764bbf0e41946b1b70867dea9
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26c29dd5c4e5b44da83fe6d7f7501b48
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Resources.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c318058664643bc42b57ff92a2268249
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Resources/WebView Canvas.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ead0bec88c42ad74abdaef7a620c3ab8
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7e7ee87643b521b448bd7621ce08c585
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples~/WebView/WebView.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8996c57d7fa53f4599487a71ebec30e
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Data.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e40c002e8effe924b8b5f3ddb97ddb3d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Native.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a81a166dab09e034d8aae47e29264c1a
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/ReadyPlayerMe.WebView.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 292600d1b44837d4b8ce752a20ebe446
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Native/iOS.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6e243b406ad144253a21126bf5537e11
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples~/WebView.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 586c581e21fc543478f6ea771cfbe372
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Native/Android.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e39779aa2c0e24c57939568536f5a251
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/ReadyPlayerMe.WebView.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cd6e68b4e29a75641843da0d43ce8a9e
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence,
3 | # @readyplayerme/onboarding-integrations team members will be requested for
4 | # review when someone opens a pull request.
5 | * @readyplayerme/onboarding-integrations
6 |
7 |
--------------------------------------------------------------------------------
/Editor/WebViewEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 055a341980f246a47b8227fd1ae60f27
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Data/Enums.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 273ee7af478906247bc2bb04ca620999
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/WebViewPanel.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fb516f7ac0cee4b0f8345a5a60de3f42
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/IOSBuildProcessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7815194cc95d1184b87a3c5afa9863eb
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Native/IOSWebView.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 34f23676aaa2d264d96522cec1bb7cd6
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Native/WebViewBase.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4006133c1e7647240921819453a2bdfb
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/AndroidBuildProcessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0856672d812a15e459cee5e879c109d7
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Data/WebViewOptions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c2e7b3ac595c8ae44a8b630554a88612
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Native/AndroidWebView.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 85aab8a3c381e194cbe00aec8df3fa60
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Ready Player Me Unity SDK Technical Support
2 |
3 | Please visit the online documentation and join our public Discord community.
4 |
5 |  **[Online Documentation]( https://readyplayer.me/docs )**
6 |
7 |  **[Discord Community]( https://discord.gg/9veRUu2 )**
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/auto-release-action.yml:
--------------------------------------------------------------------------------
1 | name: Automatic Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 |
10 | build:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: ncipollo/release-action@v1
17 | with:
18 | name: "${{github.ref_name}}"
19 | bodyFile: ".github/latest.md"
--------------------------------------------------------------------------------
/Runtime/Data/WebViewOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ReadyPlayerMe.WebView
2 | {
3 | public class WebViewOptions
4 | {
5 | public bool Transparent = true;
6 | public bool Zoom = false;
7 | public bool EnableWKWebView = true;
8 | public string UserAgent = string.Empty;
9 | public ColorMode ColorMode = ColorMode.DarkModeOff;
10 | public WebkitContentMode ContentMode = WebkitContentMode.Recommended;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ReadyPlayerMe.WebView.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReadyPlayerMe.WebView",
3 | "rootNamespace": "",
4 | "references": [
5 | "GUID:96af4ea235d92d245a095007c6ca3701"
6 | ],
7 | "includePlatforms": [],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/Editor/ReadyPlayerMe.WebView.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReadyPlayerMe.WebView.Editor",
3 | "rootNamespace": "",
4 | "references": [],
5 | "includePlatforms": [
6 | "Editor"
7 | ],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/Runtime/Data/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace ReadyPlayerMe.WebView
2 | {
3 | ///
4 | /// Defines the color mode options used to adjust the Ready Player Me website theme.
5 | ///
6 | public enum ColorMode
7 | {
8 | SystemSetting = 0,
9 | DarkModeOff = 1,
10 | DarkModeOn = 2
11 | }
12 |
13 | ///
14 | /// Defines the option that effect how the web page is displayed.
15 | ///
16 | public enum WebkitContentMode
17 | {
18 | Recommended = 0,
19 | Mobile = 1,
20 | Desktop = 2
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. e.g. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/Runtime/Native/Android/WebViewPlugin.aar.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0f86811f6f8bf1b4281c16d608492def
3 | PluginImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | iconMap: {}
7 | executionOrder: {}
8 | defineConstraints: []
9 | isPreloaded: 0
10 | isOverridable: 0
11 | isExplicitlyReferenced: 0
12 | validateReferences: 1
13 | platformData:
14 | - first:
15 | Android: Android
16 | second:
17 | enabled: 1
18 | settings: {}
19 | - first:
20 | Any:
21 | second:
22 | enabled: 0
23 | settings: {}
24 | - first:
25 | Editor: Editor
26 | second:
27 | enabled: 0
28 | settings:
29 | DefaultValueInitialized: true
30 | userData:
31 | assetBundleName:
32 | assetBundleVariant:
33 |
--------------------------------------------------------------------------------
/Runtime/Native/iOS/WebView.mm.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2cabb4f60971742a28f3bd04e65de504
3 | PluginImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | iconMap: {}
7 | executionOrder: {}
8 | defineConstraints: []
9 | isPreloaded: 0
10 | isOverridable: 0
11 | isExplicitlyReferenced: 0
12 | validateReferences: 1
13 | platformData:
14 | - first:
15 | Any:
16 | second:
17 | enabled: 0
18 | settings: {}
19 | - first:
20 | Editor: Editor
21 | second:
22 | enabled: 0
23 | settings:
24 | DefaultValueInitialized: true
25 | - first:
26 | iPhone: iOS
27 | second:
28 | enabled: 1
29 | settings:
30 | AddToEmbeddedBinaries: false
31 | userData:
32 | assetBundleName:
33 | assetBundleVariant:
34 |
--------------------------------------------------------------------------------
/Runtime/Native/iOS/WebViewWithUIWebView.mm.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fc99cbfa2b53248b18d60e327b478581
3 | PluginImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | iconMap: {}
7 | executionOrder: {}
8 | defineConstraints: []
9 | isPreloaded: 0
10 | isOverridable: 0
11 | isExplicitlyReferenced: 0
12 | validateReferences: 1
13 | platformData:
14 | - first:
15 | Any:
16 | second:
17 | enabled: 0
18 | settings: {}
19 | - first:
20 | Editor: Editor
21 | second:
22 | enabled: 0
23 | settings:
24 | DefaultValueInitialized: true
25 | - first:
26 | iPhone: iOS
27 | second:
28 | enabled: 1
29 | settings:
30 | AddToEmbeddedBinaries: false
31 | userData:
32 | assetBundleName:
33 | assetBundleVariant:
34 |
--------------------------------------------------------------------------------
/Editor/WebViewEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | namespace ReadyPlayerMe.WebView.Editor
5 | {
6 | public class WebViewEditor : UnityEditor.Editor
7 | {
8 | private const string WEB_VIEW_CANVAS_FILE_NAME = "WebView Canvas";
9 |
10 | ///
11 | /// Loads a WebView Canvas prefab to the current scene.
12 | ///
13 | [MenuItem("GameObject/UI/Ready Player Me/WebView Canvas", false)]
14 | private static void LoadWebViewCanvas()
15 | {
16 | var prefab = Resources.Load(WEB_VIEW_CANVAS_FILE_NAME);
17 | GameObject instance = Instantiate(prefab);
18 | instance.name = WEB_VIEW_CANVAS_FILE_NAME;
19 | Selection.activeGameObject = instance;
20 | EditorUtility.SetDirty(instance);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.githooks/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | COMMIT_MESSAGE="$(head -n1 "$1")"
4 | COMMIT_MESSAGE_REGEX="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z ]+\))?: .+$"
5 | AUTO_COMMIT_MESSAGE_REGEX="^Merge (pull request|branch) [a-zA-Z0-9#'_/-]+ (of [a-zA-Z0-9#':_. /-]+ )?(from|into) [a-zA-Z0-9#':_. /-]+$"
6 |
7 |
8 | if [[ $COMMIT_MESSAGE =~ $COMMIT_MESSAGE_REGEX || $COMMIT_MESSAGE =~ $AUTO_COMMIT_MESSAGE_REGEX ]]; then
9 | exit 0
10 | else
11 | echo "Invalid commit message format:"
12 | echo "Your message: '${COMMIT_MESSAGE}'"
13 | echo ""
14 | echo "Please use the following format:"
15 | echo "(): "
16 | echo ""
17 | echo " the () part is optional"
18 | echo ""
19 | echo "Examples:"
20 | echo "feat(login): add support for email login"
21 | echo "fix: fix issue with user profile image upload"
22 | exit 1
23 | fi
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The zlib License
2 | -------------------------------------
3 | Copyright (C) 2021 Ready Player Me
4 | Copyright (C) 2012 GREE, Inc.
5 | Copyright (C) 2011 Keijiro Takahashi
6 |
7 | This software is provided 'as-is', without any express or implied
8 | warranty. In no event will the authors be held liable for any damages
9 | arising from the use of this software.
10 |
11 | Permission is granted to anyone to use this software for any purpose,
12 | including commercial applications, and to alter it and redistribute it
13 | freely, subject to the following restrictions:
14 |
15 | 1. The origin of this software must not be misrepresented; you must not
16 | claim that you wrote the original software. If you use this software
17 | in a product, an acknowledgment in the product documentation would be
18 | appreciated but is not required.
19 | 2. Altered source versions must be plainly marked as such, and must not be
20 | misrepresented as being the original software.
21 | 3. This notice may not be removed or altered from any source distribution.
22 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ## [TICKETID](https://ready-player-me.atlassian.net/browse/TICKETID)
7 |
8 | ## Description
9 |
10 | - Briefly describe what this change will do
11 |
12 |
13 |
14 |
15 | ## Changes
16 |
17 | #### Added
18 |
19 | - List your additions and new features here.
20 |
21 | #### Updated
22 |
23 | - List your updates and changes here.
24 |
25 | #### Removed
26 |
27 | - List what is removed here.
28 |
29 |
30 |
31 | ## How to Test
32 |
33 | - Add steps to locally test these changes
34 |
35 |
36 |
37 | ## Checklist
38 |
39 | - [ ] Tests written or updated for the changes.
40 | - [ ] Documentation is updated.
41 | - [ ] Changelog is updated.
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Third Party Notices.md:
--------------------------------------------------------------------------------
1 | This package contains third-party software components governed by the license(s) indicated below:
2 | ---------
3 |
4 | Component Name: Unity Webview
5 |
6 | License Type: zlib License
7 |
8 | The zlib License
9 | -------------------------------------
10 | Copyright (C) 2021 Ready Player Me
11 | Copyright (C) 2012 GREE, Inc.
12 | Copyright (C) 2011 Keijiro Takahashi
13 |
14 | This software is provided 'as-is', without any express or implied
15 | warranty. In no event will the authors be held liable for any damages
16 | arising from the use of this software.
17 |
18 | Permission is granted to anyone to use this software for any purpose,
19 | including commercial applications, and to alter it and redistribute it
20 | freely, subject to the following restrictions:
21 |
22 | 1. The origin of this software must not be misrepresented; you must not
23 | claim that you wrote the original software. If you use this software
24 | in a product, an acknowledgment in the product documentation would be
25 | appreciated but is not required.
26 | 2. Altered source versions must be plainly marked as such, and must not be
27 | misrepresented as being the original software.
28 | 3. This notice may not be removed or altered from any source distribution.
29 |
--------------------------------------------------------------------------------
/.githooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Gitflow branching and naming strategy enforcement for Windows and Mac
4 |
5 | protected_branches="^(main|develop|hotfix/|release/|feature/)"
6 | current_branch=$(git symbolic-ref HEAD | sed 's!refs/heads/!!')
7 |
8 | # Ensure the branch name adheres to the Gitflow naming strategy
9 | if ! [[ ${current_branch} =~ ${protected_branches} ]]; then
10 | echo "Error: The current branch '${current_branch}' does not adhere to the Gitflow naming strategy."
11 | echo "Branch names must match the following patterns: main, develop, hotfix/*, release/*, feature/*."
12 | exit 1
13 | fi
14 |
15 | # Check if pushing to the correct remote branch
16 | remote_branch=$(git for-each-ref --format='%(upstream:short)' $(git symbolic-ref -q HEAD))
17 | if [[ -z "${remote_branch}" ]]; then
18 | echo "Error: The current branch '${current_branch}' has no tracking remote branch."
19 | exit 1
20 | fi
21 |
22 | remote_name=$(echo ${remote_branch} | cut -d/ -f1)
23 | remote_branch_name=$(echo ${remote_branch} | cut -d/ -f2-)
24 |
25 | if [[ "${current_branch}" != "${remote_branch_name}" ]]; then
26 | echo "Error: The current branch '${current_branch}' must be pushed to a remote branch with the same name: '${remote_name}/${current_branch}'."
27 | exit 1
28 | fi
29 |
30 | exit 0
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.readyplayerme.webview",
3 | "version": "2.2.1",
4 | "displayName": "Ready Player Me WebView",
5 | "description": "Ready Player Me WebView helps you display an in-engine browser that helps you load RPM website where you can create avatars and receive avatar URL at the end of the process.\nWebView is mobile only and works in Android and IOS builds.",
6 | "category": "tool",
7 | "unity": "2020.3",
8 | "unityRelease": "0f1",
9 | "documentationUrl": "https://bit.ly/UnitySDKDocs",
10 | "changelogUrl": "https://docs.readyplayer.me/ready-player-me/integration-guides/unity-sdk/changelog",
11 | "licensesUrl": "https://github.com/readyplayerme/rpm-unity-sdk-webview/blob/main/LICENSE.md",
12 | "dependencies": {
13 | "com.unity.ugui": "1.0.0",
14 | "com.unity.nuget.newtonsoft-json": "3.0.2"
15 | },
16 | "keywords": [
17 | "webview",
18 | "browser",
19 | "mobile"
20 | ],
21 | "author": {
22 | "name": "Ready Player Me",
23 | "email": "info@readyplayer.me",
24 | "url": "https://readyplayer.me"
25 | },
26 | "samples": [
27 | {
28 | "displayName": "WebView",
29 | "description": "Contains a sample scene for RPM WebView usage. You can build this scene for Android and IOS to make an avatar using RPM website and receive the avatar URL at the end.",
30 | "path": "Samples~/WebView"
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Never ignore Asset meta data
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # TextMesh Pro files
20 | [Aa]ssets/TextMesh*Pro/
21 |
22 | # Autogenerated Jetbrains Rider plugin
23 | [Aa]ssets/Plugins/Editor/JetBrains*
24 |
25 | # Visual Studio cache directory
26 | .vs/
27 |
28 | # JetBrains Rider cache directory
29 | .idea/
30 |
31 | # Gradle cache directory
32 | .gradle/
33 |
34 | # Autogenerated VS/MD/Consulo solution and project files
35 | ExportedObj/
36 | .consulo/
37 | *.csproj
38 | *.unityproj
39 | *.sln
40 | *.suo
41 | *.tmp
42 | *.user
43 | *.userprefs
44 | *.pidb
45 | *.booproj
46 | *.svd
47 | *.pdb
48 | *.mdb
49 | *.opendb
50 | *.VC.db
51 |
52 | # Unity3D generated meta files
53 | *.pidb.meta
54 | *.pdb.meta
55 | *.mdb.meta
56 |
57 | # Unity3D generated file on crash reports
58 | sysinfo.txt
59 |
60 | # Builds
61 | *.apk
62 | *.unitypackage
63 |
64 | # Crashlytics generated file
65 | crashlytics-build.properties
66 |
67 | # Samples meta files
68 | **/Samples~.meta
69 | **/Samples.meta
70 |
--------------------------------------------------------------------------------
/HOW_TO_INSTALL.md:
--------------------------------------------------------------------------------
1 | # Quick Start
2 |
3 | ### Requirements
4 | - Unity Version 2020.3 or higher
5 | - [Git](https://git-scm.com) needs to be installed to fetch the Unity package. [Download here](https://git-scm.com/downloads)
6 |
7 | **1.** To add the new Ready Player Me Unity Webview to your project you can use the Unity Package Manager to import the package directly from the Git URL.
8 |
9 | **2.** With your Unity Project open, open up the Package Manager window by going to `Window > Package Manager`.
10 |
11 | 
12 |
13 | **3.** In the **Package Manager** window click on the + icon in the top left corner and select Add Package From Git URL.
14 |
15 | 
16 |
17 | **4.** Paste in this url
18 |
19 | `https://github.com/readyplayerme/rpm-unity-sdk-webview.git`
20 |
21 | 
22 |
23 | **5.** Click add and wait for the import process to finish.
24 |
25 | After the process is complete you project will have imported these packages:
26 |
27 | - **Ready Player Me WebView**
28 |
29 | 
30 |
31 | ## Alternate Installation
32 |
33 | ### Using Git URL
34 |
35 | 1. Navigate to your project's Packages folder and open the manifest.json file.
36 | 2. Add this line below the `"dependencies": {` line
37 | - ```json title="Packages/manifest.json"
38 | "com.readyplayerme.core": "https://github.com/readyplayerme/rpm-unity-sdk-webview.git",
39 | ```
40 | 3. UPM should now install the package.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Files**
14 |
15 | Attach or link to .glb files or avatar URL that trigger the bug.
16 |
17 | In addition, make sure to run those files through the example scenes first. If you encounter errors or warnings, try to make sure they are not responsible for the issue.
18 |
19 | > Note: You have to ZIP archive them first in order for GitHub to accept the upload.
20 | If your files are confidential:
21 |
22 | - Try to create a similar, but intellectual-property-free Unity example project or build that reproduces the bug in the same way (so any community member can have a look)
23 | - Otherwise, still create this issue and send the files (or a link to them) discretely via email
24 |
25 | **To Reproduce**
26 | Steps to reproduce the behavior:
27 | 1. Go to '...'
28 | 2. Click on '....'
29 | 3. Scroll down to '....'
30 | 4. See error
31 |
32 | **Expected behavior**
33 | A clear and concise description of what you expected to happen.
34 |
35 | **Screenshots**
36 | If applicable, add screenshots to help explain your problem.
37 |
38 | **Desktop (please complete the following information):**
39 | - Ready Player Me Core version
40 | - Ready Player Me Avatar Loader version
41 | - Ready Player Me WebView version
42 | - glTFast version
43 | - Unity Editor version [e.g. 2021.2.1f1]
44 | - Render Pipeline and version [e.g. Universal Render Pipeline 12.0]
45 | - Operating System [e.g. Windows, Mac, Linux ]
46 | - Platform: [e.g. Editor , Windows Player, iOS]
47 |
48 | additionally (if significant for the bug):
49 |
50 | - Device: [e.g. iPhone6]
51 | - OS: [e.g. iOS8.1]
52 | - For WebGL: Browser [e.g. stock browser, safari]
53 |
54 | **Additional context**
55 | Add any other context about the problem here.
--------------------------------------------------------------------------------
/.github/workflows/pr-test-runner.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 |
3 | on:
4 | pull_request:
5 | types: [ opened, reopened ]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | runAllTests:
10 | name: ${{ matrix.unityVersion }} ${{ matrix.testMode }} tests
11 | runs-on: ubuntu-latest
12 | timeout-minutes: 15
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | testMode:
17 | - playmode
18 | - editmode
19 | unityVersion:
20 | - 2020.3.16f1
21 | steps:
22 | - name: Checkout Unity-SDK Repository
23 | uses: actions/checkout@v3
24 | with:
25 | repository: "readyplayerme/Unity-SDK"
26 | submodules: true
27 | fetch-depth: 0
28 | ref: develop
29 | token: ${{ secrets.DEV_SDK_TOKEN }}
30 |
31 | - name: Checkout submodule branch
32 | run: |
33 | cd Assets/Ready\ Player\ Me/WebView
34 | git fetch -a
35 | git checkout ${{ github.event.pull_request.head.ref }}
36 | git pull origin ${{ github.event.pull_request.head.ref }}
37 | - name: Cache Project
38 | uses: actions/cache@v3
39 | with:
40 | path: Library
41 | key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
42 | restore-keys: |
43 | Library-
44 |
45 | - name: Run Tests
46 | uses: game-ci/unity-test-runner@v2
47 | env:
48 | UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
49 | UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
50 | UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
51 | with:
52 | unityVersion: ${{ matrix.unityVersion }}
53 | testMode: ${{ matrix.testMode }}
54 | projectPath: ${{ matrix.projectPath }}
55 | checkName: ${{ matrix.unityVersion }} ${{ matrix.testMode }} tests result
56 | githubToken: ${{ secrets.GITHUB_TOKEN }}
57 | coverageOptions: "generateAdditionalMetrics;generateHtmlReport;generateBadgeReport;assemblyFilters:+my.assembly.*"
58 |
--------------------------------------------------------------------------------
/Editor/IOSBuildProcessor.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_IOS
2 | using System;
3 | using System.IO;
4 | using UnityEditor.Callbacks;
5 | using UnityEditor;
6 | using UnityEngine;
7 |
8 | namespace ReadyPlayerMe.WebView.Editor
9 | {
10 | public class IOSBuildProcessor
11 | {
12 |
13 | [PostProcessBuild(100)]
14 | public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
15 | {
16 | if (buildTarget != BuildTarget.iOS) return;
17 |
18 | var projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
19 | var type = Type.GetType("UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode");
20 | if (type == null)
21 | {
22 | Debug.LogError("unitywebview: failed to get PBXProject. please install iOS build support.");
23 | return;
24 | }
25 | var src = File.ReadAllText(projPath);
26 | var proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
27 | {
28 | var method = type.GetMethod("ReadFromString");
29 | method.Invoke(proj, new object[] { src });
30 | }
31 | var target = "";
32 | {
33 | var method = type.GetMethod("GetUnityFrameworkTargetGuid");
34 | target = (string) method.Invoke(proj, null);
35 | }
36 |
37 | {
38 | var method = type.GetMethod("AddFrameworkToProject");
39 | method.Invoke(proj, new object[] { target, "WebKit.framework", false });
40 | }
41 | var cflags = "";
42 | if (EditorUserBuildSettings.development)
43 | {
44 | cflags += " -DUNITYWEBVIEW_DEVELOPMENT";
45 | }
46 | #if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
47 | cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS";
48 | #endif
49 | cflags = cflags.Trim();
50 | if (!string.IsNullOrEmpty(cflags))
51 | {
52 | var method = type.GetMethod("AddBuildProperty", new Type[] { typeof(string), typeof(string), typeof(string) });
53 | method.Invoke(proj, new object[] { target, "OTHER_CFLAGS", cflags });
54 | }
55 | var dst = "";
56 | {
57 | var method = type.GetMethod("WriteToString");
58 | dst = (string) method.Invoke(proj, null);
59 | }
60 | File.WriteAllText(projPath, dst);
61 | }
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## [2.2.1] - 2024.25.04
7 |
8 | ### Fixed
9 | - feat: update install description by @rk132 in [#37](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/37)
10 |
11 | ## [2.2.0] - 2024.24.01
12 |
13 | ### Updated
14 | - feat: update install description by @rk132 in [#33](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/33)
15 |
16 | ## [2.1.3] - 2024.17.01
17 |
18 | ### Fixed
19 | - fix for deprecated PBXProject function
20 |
21 | ## [2.1.2] - 2024.11.01
22 |
23 | ### Fixed
24 | - an issue causing errors when targeting iOS
25 |
26 | ## [2.1.1] - 2024.09.01
27 |
28 | ### Fixed
29 | - fixed a flaw in the logic for disabling android build processor
30 | - an error causing android builds to fail
31 |
32 | ## [2.1.0] - 2024.08.01
33 |
34 | ### Added
35 | - scripting define symbol `RPM_DISABLE_WEBVIEW_PERMISSIONS` can now be used to disable Android permissions override @harrisonhough in [#26](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/26)
36 |
37 | ## [2.0.0] - 2023.20.04
38 |
39 | ### Updated
40 | - moved core iframe and url logic to Core package @harrisonhough in [#21](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/21)
41 |
42 | ## [1.2.1] - 2023.08.14
43 |
44 | ### Fixed
45 | - fix for missing prefab reference [#18](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/18)
46 |
47 | ### Updated
48 | - README.md updated with new guides [#18](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/18)
49 |
50 | ## [1.2.0] - 2023.05.29
51 | - added support for account linking for auto login [#14](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/14)
52 | - added support for asset unlock events [#15](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/15)
53 |
54 | ## [1.1.1] - 2023.04.20
55 |
56 | ### Updated
57 | - exposed WebViewPanel loaded field to check when canvas is ready [#13](https://github.com/readyplayerme/rpm-unity-sdk-webview/pull/13)
58 |
59 | ## [1.1.0] - 2023.03.21
60 |
61 | ### Fixed
62 | - permission popup now shows even if app loses focus
63 |
64 | ## [1.0.0] - 2023.02.20
65 |
66 | ### Added
67 | - optional sdk logging
68 | - release git actions
69 |
70 | ### Updated
71 | - Partner subdomain now comes from CoreSettings object
72 |
73 | ### Fixed
74 | - Various other bug fixes and improvements
75 |
76 | ## [0.2.0] - 2023.02.08
77 |
78 | ### Added
79 | - optional sdk logging
80 | - release git actions
81 |
82 | ### Updated
83 | - Partner subdomain now comes from CoreSettings object
84 |
85 | ### Fixed
86 | - Various other bug fixes and improvements
87 |
88 | ## [0.1.0] - 2023.01.10
89 |
90 | ### Added
91 | - WebView check to detect outdated browsers
92 | - inline code documentation
93 | - Contribution guide, code of conduct and 3rd party notices
94 | - Added sample from core module
95 |
96 | ### Updated
97 | - A big refactor of code and classes
98 |
99 | ### Fixed
100 | - Various bug fixes and improvements
101 |
--------------------------------------------------------------------------------
/Runtime/Native/WebViewBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ReadyPlayerMe.Core.WebView;
3 | using UnityEngine;
4 |
5 | namespace ReadyPlayerMe.WebView
6 | {
7 | public abstract class WebViewBase : MonoBehaviour
8 | {
9 | // Callbacks
10 | public Action OnJS;
11 | public Action OnError;
12 | public Action OnHttpError;
13 | public Action OnStarted;
14 | public Action OnLoaded;
15 | public Action OnHooked;
16 |
17 | // Cached window margins
18 | protected int marginLeft;
19 | protected int marginTop;
20 | protected int marginRight;
21 | protected int marginBottom;
22 |
23 | protected MessagePanel messageCanvas;
24 | protected int windowVisibleDisplayFrameHeight;
25 |
26 | private void OnApplicationFocus(bool hasFocus)
27 | {
28 | if (hasFocus)
29 | {
30 | AskPermission();
31 | }
32 | }
33 |
34 | public abstract void AskPermission();
35 |
36 | public abstract void Init(WebViewOptions options);
37 |
38 | public abstract void SetMargins(int left, int top, int right, int bottom);
39 |
40 | public abstract void LoadURL(string url);
41 |
42 | public abstract void LoadHTML(string html, string baseUrl);
43 |
44 | public abstract void EvaluateJS(string js);
45 |
46 | public abstract int Progress { get; }
47 |
48 | #region Properties
49 |
50 | // Window visibility
51 | protected bool isVisible = false;
52 | public abstract bool IsVisible { get; set; }
53 |
54 | // Keyboard visibility
55 | protected bool iskeyboardVisible = false;
56 | public abstract bool IsKeyboardVisible { get; set; }
57 |
58 | // Alert Dialog
59 | protected bool alertDialogEnabled = true;
60 | public abstract bool AlertDialogEnabled { get; set; }
61 |
62 | // Scroll Bounce
63 | protected bool scrollBounceEnabled = true;
64 | public abstract bool ScrollBounceEnabled { get; set; }
65 |
66 | #endregion
67 |
68 | #region Navigation Methods
69 |
70 | public abstract bool CanGoBack();
71 |
72 | public abstract bool CanGoForward();
73 |
74 | public abstract void GoBack();
75 |
76 | public abstract void GoForward();
77 |
78 | public abstract void Reload();
79 |
80 | #endregion
81 |
82 | #region Session Related Methods
83 |
84 | public abstract void AddCustomHeader(string key, string value);
85 |
86 | public abstract string GetCustomHeaderValue(string key);
87 |
88 | public abstract void RemoveCustomHeader(string key);
89 |
90 | public abstract void ClearCustomHeader();
91 |
92 | public abstract void ClearCookies();
93 |
94 | public abstract void SaveCookies();
95 |
96 | public abstract string GetCookies(string url);
97 |
98 | public abstract void SetBasicAuthInfo(string userName, string password);
99 |
100 | public abstract void ClearCache(bool includeDiskFiles);
101 |
102 | #endregion
103 |
104 | #region Callback Methods
105 |
106 | public void CallFromJS(string message)
107 | {
108 | OnJS?.Invoke(message);
109 | }
110 |
111 | public void CallOnHooked(string message)
112 | {
113 | OnHooked?.Invoke(message);
114 | }
115 |
116 | public void CallOnLoaded(string url)
117 | {
118 | OnLoaded?.Invoke(url);
119 | }
120 |
121 | public void CallOnStarted(string url)
122 | {
123 | OnStarted?.Invoke(url);
124 | }
125 |
126 | public void CallOnError(string error)
127 | {
128 | OnError?.Invoke(error);
129 | }
130 |
131 | public void CallOnHttpError(string error)
132 | {
133 | OnHttpError?.Invoke(error);
134 | }
135 |
136 | #endregion
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind
22 | * Trolling, insulting or derogatory comments, and personal or political attacks
23 | * Public or private harassment
24 | * Publishing others' private information, such as a physical or email address, without their explicit permission
25 | * Contacting individual members, contributors, or leaders privately, outside designated community mechanisms, without their explicit permission
26 | * Other conduct which could reasonably be considered inappropriate in a professional setting
27 |
28 | ## Enforcement Responsibilities
29 |
30 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
31 |
32 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
33 |
34 | ## Scope
35 |
36 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37 |
38 | ## Enforcement
39 |
40 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@readyplayer.me. All complaints will be reviewed and investigated promptly and fairly.
41 |
42 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
43 |
44 | ## Enforcement Guidelines
45 |
46 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
47 |
48 | ### 1. Correction
49 |
50 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
51 |
52 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
53 |
54 | ### 2. Warning
55 |
56 | **Community Impact**: A violation through a single incident or series of actions.
57 |
58 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
59 |
60 | ### 3. Temporary Ban
61 |
62 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
63 |
64 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
65 |
66 | ### 4. Permanent Ban
67 |
68 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
69 |
70 | **Consequence**: A permanent ban from any sort of public interaction within the community.
71 |
72 | ## Attribution
73 |
74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at .
75 |
76 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
77 |
78 | [homepage]: https://www.contributor-covenant.org
79 |
80 | For answers to common questions about this code of conduct, see the FAQ at . Translations are available at .
--------------------------------------------------------------------------------
/Runtime/Native/AndroidWebView.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_ANDROID
2 | using UnityEngine;
3 | using UnityEngine.Android;
4 |
5 | namespace ReadyPlayerMe.WebView
6 | {
7 | public class AndroidWebView : WebViewBase
8 | {
9 | private const string WebViewAndroidPluginName = "net.gree.unitywebview.CWebViewPlugin";
10 | private const string UNITYPLAYER = "com.unity3d.player.UnityPlayer";
11 | private const string HEIGHT_METHOD_NAME = "height";
12 |
13 | private AndroidJavaObject webView;
14 | private AndroidJavaObject rectangle;
15 |
16 | public override void AskPermission()
17 | {
18 | if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
19 | {
20 | Permission.RequestUserPermission(Permission.Camera);
21 | }
22 | }
23 |
24 | public override void Init(WebViewOptions options)
25 | {
26 | AskPermission();
27 |
28 | webView = new AndroidJavaObject(WebViewAndroidPluginName);
29 | webView.Call("Init", name, options.Transparent, options.Zoom, (int)options.ColorMode, options.UserAgent);
30 |
31 | using (AndroidJavaClass unityPlayer = new AndroidJavaClass(UNITYPLAYER))
32 | {
33 | AndroidJavaObject currentActivity = unityPlayer.GetStatic("currentActivity");
34 | AndroidJavaObject view = currentActivity.Get("mUnityPlayer")
35 | .Call("getView");
36 | rectangle = new AndroidJavaObject("android.graphics.Rect");
37 |
38 | view.Call("getWindowVisibleDisplayFrame", rectangle);
39 | windowVisibleDisplayFrameHeight = rectangle.Call(HEIGHT_METHOD_NAME);
40 | }
41 | }
42 |
43 | public override void SetMargins(int left, int top, int right, int bottom)
44 | {
45 | bottom = AdjustBottomMargin(bottom);
46 | webView.Call("SetMargins", left, top, right, bottom);
47 | }
48 |
49 | private int AdjustBottomMargin(int bottom)
50 | {
51 | if (IsKeyboardVisible)
52 | {
53 | int keyboardHeight = windowVisibleDisplayFrameHeight - rectangle.Call(HEIGHT_METHOD_NAME);
54 | return (bottom > keyboardHeight) ? bottom : keyboardHeight;
55 | }
56 |
57 | return bottom;
58 | }
59 |
60 | public override bool IsVisible
61 | {
62 | get { return isVisible; }
63 | set
64 | {
65 | isVisible = value;
66 | webView.Call("SetVisibility", value);
67 | }
68 | }
69 |
70 | public override bool IsKeyboardVisible
71 | {
72 | get { return iskeyboardVisible; }
73 | set
74 | {
75 | iskeyboardVisible = value;
76 | SetMargins(marginLeft, marginTop, marginRight, marginBottom);
77 | }
78 | }
79 |
80 | public override bool AlertDialogEnabled
81 | {
82 | get { return alertDialogEnabled; }
83 | set
84 | {
85 | alertDialogEnabled = value;
86 | webView.Call("SetAlertDialogEnabled", value);
87 | }
88 | }
89 |
90 | public override bool ScrollBounceEnabled
91 | {
92 | get { return scrollBounceEnabled; }
93 | set { scrollBounceEnabled = value; }
94 | }
95 |
96 | public override void LoadURL(string url)
97 | {
98 | webView.Call("LoadURL", url);
99 | }
100 |
101 | public override void LoadHTML(string html, string baseUrl)
102 | {
103 | webView.Call("LoadHTML", html, baseUrl);
104 | }
105 |
106 | public override void EvaluateJS(string js)
107 | {
108 | webView.Call("EvaluateJS", js);
109 | }
110 |
111 |
112 | #region Navigation Methods
113 |
114 | public override int Progress => webView.Get("progress");
115 |
116 | public override bool CanGoBack() => webView.Get("canGoBack");
117 |
118 | public override bool CanGoForward() => webView.Get("canGoForward");
119 |
120 | public override void GoBack() => webView.Call("GoBack");
121 |
122 | public override void GoForward() => webView.Call("GoForward");
123 |
124 | public override void Reload() => webView.Call("Reload");
125 |
126 | #endregion
127 |
128 | #region Session Related Methods
129 |
130 | public override void AddCustomHeader(string key, string value) => webView.Call("AddCustomHeader", key, value);
131 |
132 | public override string GetCustomHeaderValue(string key) => webView.Call("GetCustomHeaderValue", key);
133 |
134 | public override void RemoveCustomHeader(string key) => webView.Call("RemoveCustomHeader", key);
135 |
136 | public override void ClearCustomHeader() => webView.Call("ClearCustomHeader");
137 |
138 | public override void ClearCookies() => webView.Call("ClearCookies");
139 |
140 | public override void SaveCookies() => webView.Call("SaveCookies");
141 |
142 | public override string GetCookies(string url) => webView.Call("GetCookies", url);
143 |
144 | public override void SetBasicAuthInfo(string userName, string password) =>
145 | webView.Call("SetBasicAuthInfo", userName, password);
146 |
147 | public override void ClearCache(bool includeDiskFiles) => webView.Call("ClearCache", includeDiskFiles);
148 |
149 | #endregion
150 |
151 | private void OnDestroy()
152 | {
153 | webView.Call("Destroy");
154 | webView = null;
155 | }
156 | }
157 | }
158 | #endif
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ready Player Me Unity SDK - WebView Module
2 |
3 | [](https://github.com/readyplayerme/rpm-unity-sdk-webview/releases/latest) [](https://github.com/readyplayerme/rpm-unity-sdk-webview/discussions)
4 |
5 | ---
6 | # 🚨 Deprecation Notice 🚨
7 |
8 | **⚠️ Important:** This repository is **deprecated** and will no longer be actively maintained by Ready Player Me.
9 |
10 | We recommend switching to one of the many other Unity WebView solutions:
11 |
12 | - [**Gree Unity Webview**](https://github.com/gree/unity-webview) (Free)
13 | - [**Vuplex WebView**](https://assetstore.unity.com/search#q=vuplex) (Paid)
14 | - [**UniWebView**](https://assetstore.unity.com/packages/tools/network/uniwebview-5-229334?srsltid=AfmBOoqyAmKJu18Vl0Q-yBcYxcaiulrzERCYeNnXtKEyQ1S5L780Dwz4) (Paid)
15 |
16 | This decision has been made because it no longer makes sense for us to continue supporting this module.
17 |
18 | We appreciate your understanding and thank you for using our package.
19 |
20 | If you have any questions, feel free to reach out! 😊
21 |
22 | ---
23 |
24 | **Ready Player Me WebView** is an extension to www.readyplayer.me avatar platform and is an optional part of the Ready Player Me Unity SDK, which helps you load and display avatars created on the website.
25 |
26 | Please visit the online documentation and join our public `Forums` community.
27 |
28 |  **[Online Documentation]( https://docs.readyplayer.me/ready-player-me/integration-guides/unity)**
29 |
30 |  **[Forums](https://forum.readyplayer.me/)**
31 |
32 | :octocat: **[GitHub Discussions]( https://github.com/readyplayerme/rpm-unity-sdk-webview/discussions )**
33 |
34 | ## How to install
35 |
36 | Ready Player me Webview module requires, that you have core package installed.
37 | Please refer to the **[Quick Start guide]( https://github.com/readyplayerme/Unity-Core#readme )** for instructions on how to install the Unity Core module.
38 |
39 | The installation steps for the Webview module can be found **[here.](HOW_TO_INSTALL.md)**
40 |
41 | Users can create Ready Player Me avatars seamlessly in a WebView displayed within a Unity application.
42 |
43 | ## Prerequisites
44 |
45 | **Ready Player Me Core:** You need the Unity Ready Player Me Core for Unity installed in your project to retrieve avatars. See the [Quickstart guide](https://docs.readyplayer.me/ready-player-me/integration-guides/unity/quickstart)
46 | for instructions.
47 |
48 | **Deploying the app.** In order to test your WebView app, you have to deploy it to a physical or virtual device. See the Unity documentation on how to do that.
49 |
50 | **Required permissions.** The Ready Player Me WebView module requires the following permissions to enable all features:
51 | - Camera access
52 | - Microphone access
53 | - Local storage access
54 | - Photo Gallery access
55 | - Hardware acceleration
56 | - AllowBackup
57 |
58 | We provide a script that will automatically add these permissions for Android called AndroidBuildProcessor.cs which is is enabled by default.
59 | To disable this just add the following define to your project via the player settings for Android build target: `RPM_DISABLE_WEBVIEW_PERMISSIONS`
60 |
61 | ## Project setup (Android and iOS)
62 |
63 | Creating a Scene with a WebView in your Unity project is the same for Android and iOS.
64 |
65 | 1. Create or open your Unity project.
66 | 2. Import the Ready Player Me Core package into your project, if you haven't done so already.
67 | 3. Navigate to the Unity package manager by going to Window > Package Manager.
68 | 4. Find the Ready Player Me WebView package under Packages - Ready Player Me.
69 | 5. Find and import the WebView Sample by clicking the Import button.
70 | 6. Now open the WebView example scene by finding it in the Projects tab under `Samples/Ready Player Me WebView/\[VERSION_NUMBER\]/WebView`.
71 | 7. In the Hierarchy tab, you will find a WebView Canvas object with a child object called WebView Panel, select the WebViewPanel.
72 | 8. In the inspector you will find a WebViewPanel script with some adjustable settings.
73 | - **Screen padding:** For adjusting the padding of the Panel UI.
74 | - **Url Config:** By adjusting this you can control the behaviour of the RPM Avatar creator.
75 | - **On Avatar Created:** This Event can be used to retrieve the avatar URL after finishing the avatar creation process.
76 | 9. Open the Build Settings to set up deployment for your chosen platform.
77 | - don't forget to add the scene to the build settings.
78 |
79 | _NOTE: The WebView example scene will not load an avatar into the unity scene, it will just return and display the avatar URL on the screen. To load an avatar in the screen you can create an avatar loader script make use of the OnAvatarCreated event on the WebViewPanel._
80 |
81 | ## Deploy on Android
82 |
83 | 1. In Build Settings, set the Platform to Android.
84 | 2. Check Development Build.
85 | 3. Click Player Settings....
86 | 4. Find `Player > Other Settings > Identification`.
87 | - Check Override Default Package Name.
88 | - Set a unique Package Name in the format com.YourCompanyName.YourProductName.
89 | 5. Find `Player > Other Settings > Under Configuration > Camera Usage Description` and put some descriptive text in this mandatory field.
90 | 6. Find `Player > Other Settings > Under Configuration > Microphone Usage Description` and put some descriptive text in this mandatory field.
91 | 7. Close the Project Settings.
92 | 8. On your device, turn on USB debugging in your Developer Options settings.
93 | 9. Connect your device to your computer.
94 | 10. Click Build and Run.
95 | 11. Once the app opens on your device, click the button. Give permissions, and off you go.
96 |
97 | **_Alternatively, you can build the APK and deploy it on your own.
98 | For release builds, see the Unity and Android documentation._**
99 |
100 | ## Deploy on iOS
101 | 1. In Build Settings, set the Platform to iOS.
102 | 2. Select Debug and check Development build.
103 | 3. Find `Player > Other Settings > Identification`.
104 | - Check Override Default Package Name.
105 | - Before you build your Project for iOS, make sure that you set the Bundle Identifier.
106 | - Set a Package Name in the format `com.YourCompanyName.YourProductName`.
107 | - Fill in the `Signing Team ID` (not required for Debug builds to complete).
108 | - You can also choose whether your app targets the simulator or an actual device. To do this, change the SDK version** >> Target SDK to Simulate SDK or Device SDK.
109 | 4. Find `Player > Other Settings > Under Configuration > Camera Usage Description` and put some descriptive text in this mandatory field.
110 | 5. Find `Player > Other Settings > Under Configuration > Microphone Usage Description` and put some descriptive text in this mandatory field.
111 | 6. Close Project Settings.
112 | 7. Click Build.
113 | 8. In the file explorer, find your Builds folder and in it the `Unity-iPhone.xcodeproj`.
114 |
115 |
116 | ## WebView 2.0 update
117 |
118 | As of WebView 2.0 a number of changes have been made to isolate the WebView module from the rest of the Ready Player Me Unity SDK.
119 |
120 | This means that the classes and logic required to work with an iFrame (AKA a WebView), that is running Web Avatar Creator have now been moved to the Ready Player Me Core module.
121 | This change enables developers to utilize API's for working with any iFrame, without requiring the installation of our WebView package.
122 |
123 | As such the following classes are now located in the Ready Player Me Core module:
124 | - WebViewMessageHelper
125 | - UrlConfig
126 | - WebMessage
127 | - MessagePanel
128 | - WebMessageHelper
129 | - WebViewEvents
130 |
131 | If you have any scripts in your Unity project that reference these classes you will need to update the namespace to use ReadyPlayerMe.Core instead of ReadyPlayerMe.WebView, be sure to update the import statements in these scripts.
132 | EG: `using ReadyPlayerMe.WebView;` should be changed to `using ReadyPlayerMe.Core;`
133 |
--------------------------------------------------------------------------------
/Editor/AndroidBuildProcessor.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_ANDROID && !RPM_DISABLE_WEBVIEW_PERMISSIONS
2 | using System.IO;
3 | using System.Xml;
4 | using UnityEditor;
5 | using System.Text;
6 | using UnityEditor.Android;
7 | using UnityEditor.Callbacks;
8 |
9 | namespace ReadyPlayerMe.WebView.Editor
10 | {
11 | ///
12 | /// Receives a callback after the Android Gradle project is generated,
13 | /// and the callback is used for generating a manifest file with required permissions.
14 | ///
15 | public class AndroidBuildProcessor : IPostGenerateGradleAndroidProject
16 | {
17 | public int callbackOrder => 1;
18 |
19 | public void OnPostGenerateGradleAndroidProject(string basePath)
20 | {
21 | var manifestPath = GetManifestPath(basePath);
22 | var androidManifest = new AndroidManifest(manifestPath);
23 |
24 | androidManifest
25 | .SetHardwareAccelerated(true)
26 | .SetUsesCleartextTraffic(true)
27 | .UseCamera()
28 | .UseMicrophone()
29 | .UseGallery()
30 | .AllowBackup();
31 |
32 | androidManifest.Save();
33 | }
34 |
35 | private string GetManifestPath(string basePath)
36 | {
37 | var pathBuilder = new StringBuilder(basePath);
38 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
39 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
40 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
41 | return pathBuilder.ToString();
42 | }
43 | }
44 |
45 | ///
46 | /// AndroidManifest.xml file that is created with necessary permissions.
47 | ///
48 | internal class AndroidXmlDocument : XmlDocument
49 | {
50 | private string manifestPath;
51 | protected XmlNamespaceManager namespaceManager;
52 | public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
53 | public readonly string ToolsXmlNamespace = "http://schemas.android.com/tools";
54 |
55 | public AndroidXmlDocument(string path)
56 | {
57 | manifestPath = path;
58 | using (var reader = new XmlTextReader(manifestPath))
59 | {
60 | reader.Read();
61 | Load(reader);
62 | }
63 |
64 | namespaceManager = new XmlNamespaceManager(NameTable);
65 | namespaceManager.AddNamespace("android", AndroidXmlNamespace);
66 | namespaceManager.AddNamespace("tools", ToolsXmlNamespace);
67 | }
68 |
69 | public string Save()
70 | {
71 | return SaveAs(manifestPath);
72 | }
73 |
74 | public string SaveAs(string path)
75 | {
76 | using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
77 | {
78 | writer.Formatting = Formatting.Indented;
79 | Save(writer);
80 | }
81 |
82 | return path;
83 | }
84 | }
85 |
86 | internal class AndroidManifest : AndroidXmlDocument
87 | {
88 | private const string NodeKey = "name";
89 | private const string UsesFeature = "uses-feature";
90 | private const string UsesPermission = "uses-permission";
91 |
92 | private const string CameraPermission = "android.permission.CAMERA";
93 | private const string CameraFeature = "android.hardware.camera";
94 |
95 | private const string MicrophonePermission = "android.permission.MICROPHONE";
96 | private const string MicrophoneFeature = "android.hardware.microphone";
97 |
98 | private const string ReadExternalStoragePermission = "android.permission.READ_EXTERNAL_STORAGE";
99 | private const string WriteExternalStoragePermission = "android.permission.Write_EXTERNAL_STORAGE";
100 |
101 | private const string UsesCleartextTrafficAttribute = "usesCleartextTraffic";
102 | private const string HardwareAcceleratedAttribute = "hardwareAccelerated";
103 |
104 | private const string XPath = "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and intent-filter/category/@android:name='android.intent.category.LAUNCHER']";
105 |
106 | private static XmlNode ActivityWithLaunchIntent = null;
107 |
108 | private readonly XmlElement ManifestElement;
109 |
110 | public AndroidManifest(string path) : base(path)
111 | {
112 | ManifestElement = SelectSingleNode("/manifest") as XmlElement;
113 | }
114 |
115 | internal XmlNode GetActivityWithLaunchIntent()
116 | {
117 | return ActivityWithLaunchIntent ?? SelectSingleNode(XPath, namespaceManager);
118 | }
119 |
120 | #region Node Edit Methods
121 |
122 | private XmlAttribute CreateAndroidAttribute(string key, string value)
123 | {
124 | XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
125 | attr.Value = value;
126 | return attr;
127 | }
128 |
129 | private XmlAttribute CreateToolsAttribute(string key, string value)
130 | {
131 | XmlAttribute attr = CreateAttribute("tools", key, ToolsXmlNamespace);
132 | attr.Value = value;
133 | return attr;
134 | }
135 |
136 | internal void UpdateAttribute(XmlElement activity, string attribute, bool enabled)
137 | {
138 | var value = enabled.ToString();
139 |
140 | if (activity.GetAttribute(attribute, AndroidXmlNamespace) != value)
141 | {
142 | activity.SetAttribute(attribute, AndroidXmlNamespace, value);
143 | }
144 | }
145 |
146 | internal void UpdateNode(string nodeName, string nodeValue)
147 | {
148 | XmlNodeList node = SelectNodes($"/manifest/{nodeName}[@android:{NodeKey}='{nodeValue}']", namespaceManager);
149 | if (node?.Count == 0)
150 | {
151 | XmlElement elem = CreateElement(nodeName);
152 | elem.Attributes.Append(CreateAndroidAttribute(NodeKey, nodeValue));
153 | ManifestElement.AppendChild(elem);
154 | }
155 | }
156 |
157 | internal void UseFeature(string feature)
158 | {
159 | UpdateNode(UsesFeature, feature);
160 | }
161 |
162 | internal void UsePermission(string permission)
163 | {
164 | UpdateNode(UsesPermission, permission);
165 | }
166 |
167 | #endregion
168 |
169 | #region AndroidManifest Options
170 |
171 | internal AndroidManifest SetUsesCleartextTraffic(bool enabled)
172 | {
173 | var activity = GetActivityWithLaunchIntent() as XmlElement;
174 | UpdateAttribute(activity, UsesCleartextTrafficAttribute, enabled);
175 | return this;
176 | }
177 |
178 | internal AndroidManifest AllowBackup()
179 | {
180 | XmlNode elem = SelectSingleNode("/manifest/application");
181 | if (elem?.Attributes != null)
182 | {
183 | elem.Attributes.Append(CreateAndroidAttribute("allowBackup", "false"));
184 | elem.Attributes.Append(CreateToolsAttribute("replace", "android:allowBackup"));
185 | }
186 |
187 | return this;
188 | }
189 |
190 | internal AndroidManifest SetHardwareAccelerated(bool enabled)
191 | {
192 | var activity = GetActivityWithLaunchIntent() as XmlElement;
193 | UpdateAttribute(activity, HardwareAcceleratedAttribute, enabled);
194 | return this;
195 | }
196 |
197 | internal AndroidManifest UseCamera()
198 | {
199 | UsePermission(CameraPermission);
200 | UseFeature(CameraFeature);
201 | return this;
202 | }
203 |
204 | internal AndroidManifest UseMicrophone()
205 | {
206 | UsePermission(MicrophonePermission);
207 | UseFeature(MicrophoneFeature);
208 | return this;
209 | }
210 |
211 | internal AndroidManifest UseGallery()
212 | {
213 | UsePermission(ReadExternalStoragePermission);
214 | UsePermission(WriteExternalStoragePermission);
215 | return this;
216 | }
217 |
218 | #endregion
219 | }
220 | }
221 | #endif
222 |
--------------------------------------------------------------------------------
/Runtime/WebViewPanel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 | using ReadyPlayerMe.Core;
4 | using ReadyPlayerMe.Core.WebView;
5 | using UnityEngine;
6 | using UnityEngine.Events;
7 |
8 | namespace ReadyPlayerMe.WebView
9 | {
10 | ///
11 | /// This class is responsible for displaying and updating the WebView UI panel and .
12 | ///
13 | public class WebViewPanel : MonoBehaviour
14 | {
15 | private const string TAG = nameof(WebViewPanel);
16 |
17 | [SerializeField] private MessagePanel messagePanel;
18 |
19 | [SerializeField] private ScreenPadding screenPadding;
20 |
21 | [SerializeField] private UrlConfig urlConfig = new UrlConfig();
22 |
23 | [Space, SerializeField] public WebViewEvent OnAvatarCreated = new WebViewEvent();
24 | [SerializeField] public WebViewEvent OnUserSet = new WebViewEvent();
25 | [SerializeField] public WebViewEvent OnUserAuthorized = new WebViewEvent();
26 | [SerializeField] public AssetUnlockEvent OnAssetUnlock = new AssetUnlockEvent();
27 | [SerializeField] public UnityEvent OnUserLogout = new UnityEvent();
28 | [SerializeField] public WebViewEvent OnUserUpdate = new WebViewEvent();
29 |
30 | private WebViewBase webViewObject = null;
31 |
32 | public bool Loaded { get; private set; }
33 |
34 | public UrlConfig GetUrlConfig()
35 | {
36 | return urlConfig;
37 | }
38 |
39 | ///
40 | /// Create WebView object attached to this .
41 | ///
42 | public void LoadWebView(string loginToken = "")
43 | {
44 | MessageType messageType = Application.internetReachability == NetworkReachability.NotReachable ? MessageType.NetworkError : MessageType.Loading;
45 |
46 | #if UNITY_EDITOR || !(UNITY_ANDROID || UNITY_IOS)
47 | messageType = MessageType.NotSupported;
48 | #else
49 | InitializeAndShowWebView(loginToken);
50 | #endif
51 | messagePanel.SetMessage(messageType);
52 | messagePanel.SetVisible(true);
53 | SetScreenPadding();
54 | }
55 |
56 | ///
57 | /// Initializes the WebView if it is not already and enables the WebView window.
58 | ///
59 | private void InitializeAndShowWebView(string loginToken = "")
60 | {
61 | if (webViewObject == null)
62 | {
63 | #if UNITY_ANDROID
64 | webViewObject = gameObject.AddComponent();
65 | #elif UNITY_IOS
66 | webViewObject = gameObject.AddComponent();
67 | #endif
68 | webViewObject.OnLoaded = OnLoaded;
69 | webViewObject.OnJS = OnWebMessageReceived;
70 |
71 | var options = new WebViewOptions();
72 | webViewObject.Init(options);
73 | var url = urlConfig.BuildUrl(loginToken);
74 | webViewObject.LoadURL(url);
75 | webViewObject.IsVisible = true;
76 | }
77 | else
78 | {
79 | SetVisible(true);
80 | }
81 | }
82 |
83 | public void ReloadWithLoginToken(string loginToken = "")
84 | {
85 | urlConfig ??= new UrlConfig();
86 | var url = urlConfig.BuildUrl(loginToken);
87 | webViewObject.LoadURL(url);
88 | }
89 |
90 | ///
91 | /// Set the WebView Panel visibility.
92 | ///
93 | public void SetVisible(bool visible)
94 | {
95 | messagePanel.SetVisible(visible);
96 | if (webViewObject != null)
97 | {
98 | webViewObject.IsVisible = visible;
99 | }
100 | }
101 |
102 | private void OnDrawGizmos()
103 | {
104 | var rectTransform = transform as RectTransform;
105 | if (rectTransform != null)
106 | {
107 | Gizmos.matrix = rectTransform.localToWorldMatrix;
108 | Gizmos.color = Color.green;
109 |
110 | var center = new Vector3((screenPadding.left - screenPadding.right) / 2f, (screenPadding.bottom - screenPadding.top) / 2f);
111 | Rect rect = rectTransform.rect;
112 | var size = new Vector3(rect.width - (screenPadding.left + screenPadding.right), rect.height - (screenPadding.bottom + screenPadding.top));
113 |
114 | Gizmos.DrawWireCube(center, size);
115 | }
116 | }
117 |
118 | // Set WebView screen padding in pixels.
119 | private void SetScreenPadding()
120 | {
121 | if (webViewObject)
122 | {
123 | webViewObject.SetMargins(screenPadding.left, screenPadding.top, screenPadding.right, screenPadding.bottom);
124 | }
125 |
126 | messagePanel.SetMargins(screenPadding.left, screenPadding.top, screenPadding.right, screenPadding.bottom);
127 | }
128 |
129 | // Receives message from RPM website, which contains avatar URL.
130 | private void OnWebMessageReceived(string message)
131 | {
132 | SDKLogger.AvatarLoaderLogger.Log(TAG, $"--- WebView Message: {message}");
133 | try
134 | {
135 | HandleEvents(JsonConvert.DeserializeObject(message));
136 | }
137 | catch (Exception e)
138 | {
139 | SDKLogger.AvatarLoaderLogger.Log(TAG, $"--- Message is not JSON: {message}\nError Message: {e.Message}");
140 | }
141 | }
142 |
143 | private void HandleEvents(WebMessage webMessage)
144 | {
145 | switch (webMessage.eventName)
146 | {
147 | case WebViewEvents.AVATAR_EXPORT:
148 | OnAvatarCreated?.Invoke(webMessage.GetAvatarUrl());
149 | HideAndClearCache();
150 | break;
151 | case WebViewEvents.USER_SET:
152 | OnUserSet?.Invoke(webMessage.GetUserId());
153 |
154 | break;
155 | case WebViewEvents.ASSET_UNLOCK:
156 | OnAssetUnlock?.Invoke(webMessage.GetAssetRecord());
157 | break;
158 | case WebViewEvents.USER_AUTHORIZED:
159 | OnUserAuthorized?.Invoke(webMessage.GetUserId());
160 | break;
161 | case WebViewEvents.USER_LOGOUT:
162 | OnUserLogout?.Invoke();
163 | break;
164 | case WebViewEvents.USER_UPDATED:
165 | OnUserUpdate?.Invoke(webMessage.GetUserId());
166 | break;
167 | }
168 | }
169 |
170 | private void HideAndClearCache()
171 | {
172 | if (urlConfig.clearCache)
173 | {
174 | Loaded = false;
175 | webViewObject.Reload();
176 | }
177 |
178 | SetVisible(false);
179 | }
180 |
181 | ///
182 | /// Receives status message when RPM website is loaded in WebView
183 | ///
184 | ///
185 | private void OnLoaded(string message)
186 | {
187 | if (Loaded) return;
188 |
189 | SDKLogger.AvatarLoaderLogger.Log(TAG, $"--- WebView Loaded.");
190 | webViewObject.EvaluateJS(@"
191 | document.cookie = 'webview=true';
192 |
193 | if (window && window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.unityControl) {
194 | window.Unity = {
195 | call: function(msg) {
196 | window.webkit.messageHandlers.unityControl.postMessage(msg);
197 | }
198 | }
199 | }
200 | else {
201 | window.Unity = {
202 | call: function(msg) {
203 | window.location = 'unity:' + msg;
204 | }
205 | }
206 | }
207 |
208 | function subscribe(event) {
209 | const json = parse(event);
210 | const source = json.source;
211 |
212 | if (source !== 'readyplayerme') {
213 | return;
214 | }
215 |
216 | Unity.call(event.data);
217 | }
218 |
219 | function parse(event) {
220 | try {
221 | return JSON.parse(event.data);
222 | } catch (error) {
223 | return null;
224 | }
225 | }
226 |
227 | window.postMessage(
228 | JSON.stringify({
229 | target: 'readyplayerme',
230 | type: 'subscribe',
231 | eventName: 'v1.**'
232 | }),
233 | '*'
234 | );
235 |
236 | window.removeEventListener('message', subscribe);
237 | window.addEventListener('message', subscribe)
238 | ");
239 |
240 | Loaded = true;
241 | }
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Welcome to the Ready Player Me Unity WebView contributing guide
2 |
3 | Thank you for investing your time in contributing to our project! Any contribution you make will be reflected on [https://github.com/readyplayerme/Unity-WebView](https://github.com/readyplayerme/Unity-WebView) :sparkles:.
4 |
5 | Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
6 |
7 | In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
8 |
9 | Use the table of contents icon on the top left corner of this document to get to a specific section of this guide quickly.
10 |
11 | ***NOTE: This repository is a custom fork of the open source [Gree WebView](https://github.com/gree/unity-webview) plugin.***
12 |
13 |
14 | ## New contributor guide
15 |
16 | To get an overview of the project, read the [README](README.md). Here are some resources to help you get started with open source contributions:
17 |
18 | - [FAQ](#faq)
19 | - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
20 | - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
21 | - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
22 | - [Code style guide](https://github.com/readyplayerme/rpm-unity-sdk-core/blob/main/style-guidelines.md)
23 |
24 |
25 | ## FAQ
26 |
27 | #### **Did you find a bug?**
28 |
29 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/readyplayerme/Unity-WebView/issues).
30 |
31 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/readyplayerme/Unity-WebView/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
32 |
33 | * If possible, use the relevant bug report templates to create the issue. Simply copy the content of the appropriate template into a .rb file, make the necessary changes to demonstrate the issue, and **paste the content into the issue description**:
34 | * [**Generic template** for other issues](https://github.com/readyplayerme/Unity-WebView/blob/develop/.github/pull_request_template.md)
35 |
36 | * If you would like to contact us directly to report an issue or for general support requests contact our Ready Player Me Support email [support@readyplayer.me](mailto:support@readyplayer.me).
37 |
38 | #### **Did you write a patch that fixes a bug?**
39 |
40 | * Open a new GitHub pull request with the patch.
41 |
42 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
43 |
44 | * Before submitting, please read the [Contributing to Ready Player Me Unity SDK](#) guide to know more about our coding conventions and best practices.
45 |
46 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
47 |
48 | Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability will generally not be accepted.
49 |
50 | #### **Do you intend to add a new feature or change an existing one?**
51 |
52 | * Suggest your change to the Ready Player Me support email [support@readyplayer.me](mailto:support@readyplayer.me) and start writing code.
53 |
54 | * **Do not open an issue on GitHub** until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes.
55 |
56 | #### **Do you have questions about the source code?**
57 |
58 | * Ask any question about how to use the Ready Player Me Unity SDK in the [Ready Player Me support email](mailto:support@readyplayer.me).
59 |
60 | ## Issues
61 |
62 | #### Create a new issue
63 |
64 | If you spot a problem with the docs, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/github/docs/issues/new/choose).
65 |
66 | #### Solve an issue
67 |
68 | Scan through our [existing issues](https://github.com/github/docs/issues) to find one that interests you. You can narrow down the search using `labels` as filters. See [Labels](/contributing/how-to-use-labels.md) for more information. As a general rule, we don’t assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
69 |
70 | ### Make Changes
71 |
72 | #### Make changes locally
73 |
74 |
75 | 1. [Install Git LFS](https://docs.github.com/en/github/managing-large-files/versioning-large-files/installing-git-large-file-storage).
76 |
77 | 2. Fork the repository.
78 | - Using GitHub Desktop:
79 | - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
80 | - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
81 |
82 | - Using the command line:
83 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
84 |
85 | 4. Create a working branch and start with your changes!
86 |
87 | ### Commit your update
88 |
89 | We encourage following the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format when it comes to writing commit messages. Our package repositories come with a .githooks folder that has a commit-msg file that can enforce this.
90 | To set this up you just need to configure git's hookspath folder to point there.
91 |
92 | You can do this by
93 | 1. Open the terminal
94 | 2. Navigate to the root folder of this repository
95 | 3. Run the following command
96 | `git config core.hooksPath .githooks`
97 |
98 | Commit the changes once you are happy with them. Don't forget to [self-review](#self-review) to speed up the review process:zap:.
99 |
100 | ### Self review
101 |
102 | You should always review your own PR first.
103 |
104 | For content changes, make sure that you:
105 |
106 | - [ ] Confirm that the changes meet the user experience and goals outlined in the content design plan (if there is one).
107 | - [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the [style guide](https://github.com/readyplayerme/rpm-unity-sdk-core/blob/main/style-guidelines.md), or content that isn't rendering due to versioning problems. Remember that lists and tables can be tricky.
108 | - [ ] Review the content for technical accuracy.
109 | - [ ] Review the entire pull request using the [translations guide for writers](./translations/for-writers.md).
110 | - [ ] Copy-edit the changes for grammar, spelling, and adherence to the [style guide](https://github.com/readyplayerme/rpm-unity-sdk-core/blob/main/style-guidelines.md).
111 | - [ ] Check new or updated Liquid statements to confirm that versioning is correct.
112 | - [ ] If there are any failing checks in your PR, troubleshoot them until they're all passing.
113 |
114 |
115 | ### Pull Request
116 |
117 | When you're finished with the changes, create a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
118 | - Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request.
119 | - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
120 | - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
121 | Once you submit your PR, a team member will review your proposal. We may ask questions or request additional information.
122 | - We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
123 | - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
124 | - If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
125 |
126 | ### Your PR is merged!
127 |
128 | Congratulations :tada::tada: The GitHub team thanks you :sparkles:.
129 |
130 | Once your PR is merged, your contributions will be publicly visible on the [GitHub docs](https://docs.github.com/en).
131 |
132 | Now that you are part of the GitHub docs community, see how else you can [contribute to the docs](/contributing/types-of-contributions.md).
133 |
134 |
135 |
136 | Thanks! :heart: :heart: :heart:
137 |
138 | Ready Player Me Team
--------------------------------------------------------------------------------
/Runtime/Native/IOSWebView.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_IOS
2 | using System;
3 | using System.Runtime.InteropServices;
4 | using UnityEngine;
5 |
6 | namespace ReadyPlayerMe.WebView
7 | {
8 | ///
9 | /// This class is responsible for displaying and interacting with the native WebBrowser on iOS devices.
10 | ///
11 | public class IOSWebView : WebViewBase
12 | {
13 | private IntPtr webView;
14 |
15 | public override void AskPermission()
16 | {
17 | if (!Application.HasUserAuthorization(UserAuthorization.WebCam))
18 | {
19 | Application.RequestUserAuthorization(UserAuthorization.WebCam);
20 | }
21 | }
22 |
23 | public override void Init(WebViewOptions options)
24 | {
25 | AskPermission();
26 |
27 | webView = _CWebViewPlugin_Init(name, options.Transparent, options.Zoom, options.UserAgent, options.EnableWKWebView,
28 | (int) options.ContentMode);
29 | }
30 |
31 | ///
32 | /// Us this to set the margins of the WebView UI.
33 | ///
34 | /// Margin from left.
35 | /// Margin from top.
36 | /// Margin from right.
37 | /// Margin from bottom.
38 | public override void SetMargins(int left, int top, int right, int bottom)
39 | {
40 | _CWebViewPlugin_SetMargins(webView, left, top, right, bottom, false);
41 | }
42 |
43 | ///
44 | /// Used to get or set the visibility of the native WebView UI.
45 | ///
46 | public override bool IsVisible
47 | {
48 | get => isVisible;
49 | set
50 | {
51 | isVisible = value;
52 | _CWebViewPlugin_SetVisibility(webView, value);
53 | }
54 | }
55 |
56 | ///
57 | /// Used to get or set the visibility of the native Keyboard UI.
58 | ///
59 | public override bool IsKeyboardVisible
60 | {
61 | get => TouchScreenKeyboard.visible;
62 | set
63 | {
64 | Debug.LogWarning("Overwritten by [TouchScreenKeyboard.visible]");
65 | iskeyboardVisible = TouchScreenKeyboard.visible;
66 | SetMargins(marginLeft, marginTop, marginRight, marginBottom);
67 | }
68 | }
69 |
70 | ///
71 | /// Used to get or set the alert dialogue.
72 | ///
73 | public override bool AlertDialogEnabled
74 | {
75 | get => alertDialogEnabled;
76 | set
77 | {
78 | alertDialogEnabled = value;
79 | _CWebViewPlugin_SetAlertDialogEnabled(webView, value);
80 | }
81 | }
82 |
83 | ///
84 | /// Used to get or set the ScrollBounce feature.
85 | ///
86 | public override bool ScrollBounceEnabled
87 | {
88 | get => scrollBounceEnabled;
89 | set
90 | {
91 | scrollBounceEnabled = value;
92 | _CWebViewPlugin_SetScrollBounceEnabled(webView, value);
93 | }
94 | }
95 |
96 | ///
97 | /// Load the in the WebView browser.
98 | ///
99 | ///
100 | public override void LoadURL(string url)
101 | {
102 | _CWebViewPlugin_LoadURL(webView, url);
103 | }
104 |
105 | ///
106 | /// Load the provided in the WebView browser from the .
107 | ///
108 | /// ///
109 | /// The custom html to load.
110 | /// The base URL to use when loading the html.
111 | public override void LoadHTML(string html, string baseUrl)
112 | {
113 | _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
114 | }
115 |
116 | ///
117 | /// This method is used to execute the javascript providede in
118 | ///
119 | /// The javascript snippet to run in the WebView.
120 | public override void EvaluateJS(string js)
121 | {
122 | _CWebViewPlugin_EvaluateJS(webView, js);
123 | }
124 |
125 | private void OnDestroy()
126 | {
127 | _CWebViewPlugin_Destroy(webView);
128 | webView = IntPtr.Zero;
129 | }
130 |
131 | #region DLL Import
132 |
133 | [DllImport("__Internal")]
134 | private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, string ua,
135 | bool enableWKWebView, int wkContentMode);
136 |
137 | [DllImport("__Internal")]
138 | private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
139 |
140 | [DllImport("__Internal")]
141 | private static extern void _CWebViewPlugin_SetMargins(IntPtr instance, float left, float top, float right,
142 | float bottom, bool relative);
143 |
144 | [DllImport("__Internal")]
145 | private static extern void _CWebViewPlugin_SetVisibility(IntPtr instance, bool visibility);
146 |
147 | [DllImport("__Internal")]
148 | private static extern void _CWebViewPlugin_SetAlertDialogEnabled(IntPtr instance, bool enabled);
149 |
150 | [DllImport("__Internal")]
151 | private static extern void _CWebViewPlugin_SetScrollBounceEnabled(IntPtr instance, bool enabled);
152 |
153 | [DllImport("__Internal")]
154 | private static extern bool _CWebViewPlugin_SetURLPattern(IntPtr instance, string allowPattern,
155 | string denyPattern, string hookPattern);
156 |
157 | [DllImport("__Internal")]
158 | private static extern void _CWebViewPlugin_LoadURL(IntPtr instance, string url);
159 |
160 | [DllImport("__Internal")]
161 | private static extern void _CWebViewPlugin_LoadHTML(IntPtr instance, string html, string baseUrl);
162 |
163 | [DllImport("__Internal")]
164 | private static extern void _CWebViewPlugin_EvaluateJS(IntPtr instance, string url);
165 |
166 | [DllImport("__Internal")]
167 | private static extern int _CWebViewPlugin_Progress(IntPtr instance);
168 |
169 | [DllImport("__Internal")]
170 | private static extern bool _CWebViewPlugin_CanGoBack(IntPtr instance);
171 |
172 | [DllImport("__Internal")]
173 | private static extern bool _CWebViewPlugin_CanGoForward(IntPtr instance);
174 |
175 | [DllImport("__Internal")]
176 | private static extern void _CWebViewPlugin_GoBack(IntPtr instance);
177 |
178 | [DllImport("__Internal")]
179 | private static extern void _CWebViewPlugin_GoForward(IntPtr instance);
180 |
181 | [DllImport("__Internal")]
182 | private static extern void _CWebViewPlugin_Reload(IntPtr instance);
183 |
184 | [DllImport("__Internal")]
185 | private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey,
186 | string headerValue);
187 |
188 | [DllImport("__Internal")]
189 | private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
190 |
191 | [DllImport("__Internal")]
192 | private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
193 |
194 | [DllImport("__Internal")]
195 | private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
196 |
197 | [DllImport("__Internal")]
198 | private static extern void _CWebViewPlugin_ClearCookies();
199 |
200 | [DllImport("__Internal")]
201 | private static extern void _CWebViewPlugin_SaveCookies();
202 |
203 | [DllImport("__Internal")]
204 | private static extern string _CWebViewPlugin_GetCookies(string url);
205 |
206 | [DllImport("__Internal")]
207 | private static extern void _CWebViewPlugin_SetBasicAuthInfo(IntPtr instance, string userName, string password);
208 |
209 | [DllImport("__Internal")]
210 | private static extern void _CWebViewPlugin_ClearCache(IntPtr instance, bool includeDiskFiles);
211 |
212 | #endregion
213 |
214 | #region Navigation Methods
215 |
216 | public override int Progress => _CWebViewPlugin_Progress(webView);
217 |
218 | public override bool CanGoBack()
219 | {
220 | return _CWebViewPlugin_CanGoBack(webView);
221 | }
222 |
223 | public override bool CanGoForward()
224 | {
225 | return _CWebViewPlugin_CanGoForward(webView);
226 | }
227 |
228 | public override void GoBack()
229 | {
230 | _CWebViewPlugin_GoBack(webView);
231 | }
232 |
233 | public override void GoForward()
234 | {
235 | _CWebViewPlugin_GoForward(webView);
236 | }
237 |
238 | public override void Reload()
239 | {
240 | _CWebViewPlugin_Reload(webView);
241 | }
242 |
243 | #endregion
244 |
245 | #region Session Related Methods
246 |
247 | public override void AddCustomHeader(string key, string value)
248 | {
249 | _CWebViewPlugin_AddCustomHeader(webView, key, value);
250 | }
251 |
252 | public override string GetCustomHeaderValue(string key)
253 | {
254 | return _CWebViewPlugin_GetCustomHeaderValue(webView, key);
255 | }
256 |
257 | public override void RemoveCustomHeader(string key)
258 | {
259 | _CWebViewPlugin_RemoveCustomHeader(webView, key);
260 | }
261 |
262 | public override void ClearCustomHeader()
263 | {
264 | _CWebViewPlugin_ClearCustomHeader(webView);
265 | }
266 |
267 | ///
268 | /// This method is used to clear the WebView browsers cache.
269 | ///
270 | /// Set to true if you also want to clear the local disk files.
271 | public override void ClearCache(bool includeDiskFiles)
272 | {
273 | _CWebViewPlugin_ClearCache(webView, includeDiskFiles);
274 | }
275 |
276 | ///
277 | /// This method is used to clear the WebView browsers cookies.
278 | ///
279 | public override void ClearCookies()
280 | {
281 | _CWebViewPlugin_ClearCookies();
282 | }
283 |
284 | ///
285 | /// This method is used get the cookies of the WebView browser.
286 | ///
287 | ///
288 | ///
289 | public override string GetCookies(string url)
290 | {
291 | return _CWebViewPlugin_GetCookies(url);
292 | }
293 |
294 | public override void SaveCookies()
295 | {
296 | _CWebViewPlugin_SaveCookies();
297 | }
298 |
299 | public override void SetBasicAuthInfo(string userName, string password)
300 | {
301 | _CWebViewPlugin_SetBasicAuthInfo(webView, userName, password);
302 | }
303 |
304 | #endregion
305 | }
306 | }
307 | #endif
308 |
--------------------------------------------------------------------------------
/Resources/WebView Canvas.prefab:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1 &512035607996807158
4 | GameObject:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | serializedVersion: 6
10 | m_Component:
11 | - component: {fileID: 3384476359650293983}
12 | - component: {fileID: 4764189938943989217}
13 | - component: {fileID: 4547605496105329212}
14 | m_Layer: 5
15 | m_Name: Message
16 | m_TagString: Untagged
17 | m_Icon: {fileID: 0}
18 | m_NavMeshLayer: 0
19 | m_StaticEditorFlags: 0
20 | m_IsActive: 1
21 | --- !u!224 &3384476359650293983
22 | RectTransform:
23 | m_ObjectHideFlags: 0
24 | m_CorrespondingSourceObject: {fileID: 0}
25 | m_PrefabInstance: {fileID: 0}
26 | m_PrefabAsset: {fileID: 0}
27 | m_GameObject: {fileID: 512035607996807158}
28 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
29 | m_LocalPosition: {x: 0, y: 0, z: 0}
30 | m_LocalScale: {x: 1, y: 1, z: 1}
31 | m_Children: []
32 | m_Father: {fileID: 8288541070304566713}
33 | m_RootOrder: 0
34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35 | m_AnchorMin: {x: 0.5, y: 0.5}
36 | m_AnchorMax: {x: 0.5, y: 0.5}
37 | m_AnchoredPosition: {x: 0, y: 0}
38 | m_SizeDelta: {x: 880, y: 500}
39 | m_Pivot: {x: 0.5, y: 0.5}
40 | --- !u!222 &4764189938943989217
41 | CanvasRenderer:
42 | m_ObjectHideFlags: 0
43 | m_CorrespondingSourceObject: {fileID: 0}
44 | m_PrefabInstance: {fileID: 0}
45 | m_PrefabAsset: {fileID: 0}
46 | m_GameObject: {fileID: 512035607996807158}
47 | m_CullTransparentMesh: 0
48 | --- !u!114 &4547605496105329212
49 | MonoBehaviour:
50 | m_ObjectHideFlags: 0
51 | m_CorrespondingSourceObject: {fileID: 0}
52 | m_PrefabInstance: {fileID: 0}
53 | m_PrefabAsset: {fileID: 0}
54 | m_GameObject: {fileID: 512035607996807158}
55 | m_Enabled: 1
56 | m_EditorHideFlags: 0
57 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
58 | m_Name:
59 | m_EditorClassIdentifier:
60 | m_Material: {fileID: 0}
61 | m_Color: {r: 1, g: 1, b: 1, a: 1}
62 | m_RaycastTarget: 1
63 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
64 | m_Maskable: 1
65 | m_OnCullStateChanged:
66 | m_PersistentCalls:
67 | m_Calls: []
68 | m_FontData:
69 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
70 | m_FontSize: 42
71 | m_FontStyle: 1
72 | m_BestFit: 1
73 | m_MinSize: 4
74 | m_MaxSize: 69
75 | m_Alignment: 4
76 | m_AlignByGeometry: 0
77 | m_RichText: 1
78 | m_HorizontalOverflow: 0
79 | m_VerticalOverflow: 0
80 | m_LineSpacing: 1
81 | m_Text: ...
82 | --- !u!1 &797135390826723026
83 | GameObject:
84 | m_ObjectHideFlags: 0
85 | m_CorrespondingSourceObject: {fileID: 0}
86 | m_PrefabInstance: {fileID: 0}
87 | m_PrefabAsset: {fileID: 0}
88 | serializedVersion: 6
89 | m_Component:
90 | - component: {fileID: 797135390826723054}
91 | - component: {fileID: 797135390826723055}
92 | - component: {fileID: 797135390826723052}
93 | - component: {fileID: 797135390826723053}
94 | - component: {fileID: 797135390826723048}
95 | - component: {fileID: 797135390826723049}
96 | m_Layer: 5
97 | m_Name: WebView Canvas
98 | m_TagString: Untagged
99 | m_Icon: {fileID: 0}
100 | m_NavMeshLayer: 0
101 | m_StaticEditorFlags: 0
102 | m_IsActive: 1
103 | --- !u!224 &797135390826723054
104 | RectTransform:
105 | m_ObjectHideFlags: 0
106 | m_CorrespondingSourceObject: {fileID: 0}
107 | m_PrefabInstance: {fileID: 0}
108 | m_PrefabAsset: {fileID: 0}
109 | m_GameObject: {fileID: 797135390826723026}
110 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
111 | m_LocalPosition: {x: 0, y: 0, z: 0}
112 | m_LocalScale: {x: 0, y: 0, z: 0}
113 | m_Children:
114 | - {fileID: 797135391978056255}
115 | m_Father: {fileID: 0}
116 | m_RootOrder: 0
117 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
118 | m_AnchorMin: {x: 0, y: 0}
119 | m_AnchorMax: {x: 0, y: 0}
120 | m_AnchoredPosition: {x: 0, y: 0}
121 | m_SizeDelta: {x: 0, y: 0}
122 | m_Pivot: {x: 0, y: 0}
123 | --- !u!223 &797135390826723055
124 | Canvas:
125 | m_ObjectHideFlags: 0
126 | m_CorrespondingSourceObject: {fileID: 0}
127 | m_PrefabInstance: {fileID: 0}
128 | m_PrefabAsset: {fileID: 0}
129 | m_GameObject: {fileID: 797135390826723026}
130 | m_Enabled: 1
131 | serializedVersion: 3
132 | m_RenderMode: 0
133 | m_Camera: {fileID: 0}
134 | m_PlaneDistance: 100
135 | m_PixelPerfect: 0
136 | m_ReceivesEvents: 1
137 | m_OverrideSorting: 0
138 | m_OverridePixelPerfect: 0
139 | m_SortingBucketNormalizedSize: 0
140 | m_AdditionalShaderChannelsFlag: 0
141 | m_SortingLayerID: 0
142 | m_SortingOrder: 1
143 | m_TargetDisplay: 0
144 | --- !u!114 &797135390826723052
145 | MonoBehaviour:
146 | m_ObjectHideFlags: 0
147 | m_CorrespondingSourceObject: {fileID: 0}
148 | m_PrefabInstance: {fileID: 0}
149 | m_PrefabAsset: {fileID: 0}
150 | m_GameObject: {fileID: 797135390826723026}
151 | m_Enabled: 1
152 | m_EditorHideFlags: 0
153 | m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
154 | m_Name:
155 | m_EditorClassIdentifier:
156 | m_UiScaleMode: 1
157 | m_ReferencePixelsPerUnit: 100
158 | m_ScaleFactor: 1
159 | m_ReferenceResolution: {x: 1080, y: 1920}
160 | m_ScreenMatchMode: 0
161 | m_MatchWidthOrHeight: 0
162 | m_PhysicalUnit: 3
163 | m_FallbackScreenDPI: 96
164 | m_DefaultSpriteDPI: 96
165 | m_DynamicPixelsPerUnit: 1
166 | m_PresetInfoIsWorld: 0
167 | --- !u!114 &797135390826723053
168 | MonoBehaviour:
169 | m_ObjectHideFlags: 0
170 | m_CorrespondingSourceObject: {fileID: 0}
171 | m_PrefabInstance: {fileID: 0}
172 | m_PrefabAsset: {fileID: 0}
173 | m_GameObject: {fileID: 797135390826723026}
174 | m_Enabled: 1
175 | m_EditorHideFlags: 0
176 | m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
177 | m_Name:
178 | m_EditorClassIdentifier:
179 | m_IgnoreReversedGraphics: 1
180 | m_BlockingObjects: 0
181 | m_BlockingMask:
182 | serializedVersion: 2
183 | m_Bits: 4294967295
184 | --- !u!114 &797135390826723048
185 | MonoBehaviour:
186 | m_ObjectHideFlags: 0
187 | m_CorrespondingSourceObject: {fileID: 0}
188 | m_PrefabInstance: {fileID: 0}
189 | m_PrefabAsset: {fileID: 0}
190 | m_GameObject: {fileID: 797135390826723026}
191 | m_Enabled: 1
192 | m_EditorHideFlags: 0
193 | m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
194 | m_Name:
195 | m_EditorClassIdentifier:
196 | m_FirstSelected: {fileID: 0}
197 | m_sendNavigationEvents: 1
198 | m_DragThreshold: 10
199 | --- !u!114 &797135390826723049
200 | MonoBehaviour:
201 | m_ObjectHideFlags: 0
202 | m_CorrespondingSourceObject: {fileID: 0}
203 | m_PrefabInstance: {fileID: 0}
204 | m_PrefabAsset: {fileID: 0}
205 | m_GameObject: {fileID: 797135390826723026}
206 | m_Enabled: 1
207 | m_EditorHideFlags: 0
208 | m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
209 | m_Name:
210 | m_EditorClassIdentifier:
211 | m_HorizontalAxis: Horizontal
212 | m_VerticalAxis: Vertical
213 | m_SubmitButton: Submit
214 | m_CancelButton: Cancel
215 | m_InputActionsPerSecond: 10
216 | m_RepeatDelay: 0.5
217 | m_ForceModuleActive: 0
218 | --- !u!1 &797135391978056252
219 | GameObject:
220 | m_ObjectHideFlags: 0
221 | m_CorrespondingSourceObject: {fileID: 0}
222 | m_PrefabInstance: {fileID: 0}
223 | m_PrefabAsset: {fileID: 0}
224 | serializedVersion: 6
225 | m_Component:
226 | - component: {fileID: 797135391978056255}
227 | - component: {fileID: 797135391978056254}
228 | m_Layer: 5
229 | m_Name: WebView Panel
230 | m_TagString: Untagged
231 | m_Icon: {fileID: 0}
232 | m_NavMeshLayer: 0
233 | m_StaticEditorFlags: 0
234 | m_IsActive: 1
235 | --- !u!224 &797135391978056255
236 | RectTransform:
237 | m_ObjectHideFlags: 0
238 | m_CorrespondingSourceObject: {fileID: 0}
239 | m_PrefabInstance: {fileID: 0}
240 | m_PrefabAsset: {fileID: 0}
241 | m_GameObject: {fileID: 797135391978056252}
242 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
243 | m_LocalPosition: {x: 0, y: 0, z: 0}
244 | m_LocalScale: {x: 1, y: 1, z: 1}
245 | m_Children:
246 | - {fileID: 8288541070304566713}
247 | m_Father: {fileID: 797135390826723054}
248 | m_RootOrder: 0
249 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
250 | m_AnchorMin: {x: 0, y: 0}
251 | m_AnchorMax: {x: 1, y: 1}
252 | m_AnchoredPosition: {x: 0, y: 0}
253 | m_SizeDelta: {x: 0, y: 0}
254 | m_Pivot: {x: 0.5, y: 0.5}
255 | --- !u!114 &797135391978056254
256 | MonoBehaviour:
257 | m_ObjectHideFlags: 0
258 | m_CorrespondingSourceObject: {fileID: 0}
259 | m_PrefabInstance: {fileID: 0}
260 | m_PrefabAsset: {fileID: 0}
261 | m_GameObject: {fileID: 797135391978056252}
262 | m_Enabled: 1
263 | m_EditorHideFlags: 0
264 | m_Script: {fileID: 11500000, guid: fb516f7ac0cee4b0f8345a5a60de3f42, type: 3}
265 | m_Name:
266 | m_EditorClassIdentifier:
267 | messagePanel: {fileID: 4513550131565409114}
268 | screenPadding:
269 | left: 0
270 | top: 0
271 | right: 0
272 | bottom: 0
273 | urlConfig:
274 | language: 0
275 | clearCache: 0
276 | gender: 0
277 | bodyType: 0
278 | OnAvatarCreated:
279 | m_PersistentCalls:
280 | m_Calls: []
281 | --- !u!1 &797135392384398425
282 | GameObject:
283 | m_ObjectHideFlags: 0
284 | m_CorrespondingSourceObject: {fileID: 0}
285 | m_PrefabInstance: {fileID: 0}
286 | m_PrefabAsset: {fileID: 0}
287 | serializedVersion: 6
288 | m_Component:
289 | - component: {fileID: 797135392384398424}
290 | - component: {fileID: 797135392384398426}
291 | - component: {fileID: 797135392384398427}
292 | m_Layer: 5
293 | m_Name: TapToClose
294 | m_TagString: Untagged
295 | m_Icon: {fileID: 0}
296 | m_NavMeshLayer: 0
297 | m_StaticEditorFlags: 0
298 | m_IsActive: 0
299 | --- !u!224 &797135392384398424
300 | RectTransform:
301 | m_ObjectHideFlags: 0
302 | m_CorrespondingSourceObject: {fileID: 0}
303 | m_PrefabInstance: {fileID: 0}
304 | m_PrefabAsset: {fileID: 0}
305 | m_GameObject: {fileID: 797135392384398425}
306 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
307 | m_LocalPosition: {x: 0, y: 0, z: 0}
308 | m_LocalScale: {x: 1, y: 1, z: 1}
309 | m_Children: []
310 | m_Father: {fileID: 8288541070304566713}
311 | m_RootOrder: 1
312 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
313 | m_AnchorMin: {x: 0, y: 0.5}
314 | m_AnchorMax: {x: 1, y: 0.5}
315 | m_AnchoredPosition: {x: 0, y: -300}
316 | m_SizeDelta: {x: 0, y: 64}
317 | m_Pivot: {x: 0.5, y: 0.5}
318 | --- !u!222 &797135392384398426
319 | CanvasRenderer:
320 | m_ObjectHideFlags: 0
321 | m_CorrespondingSourceObject: {fileID: 0}
322 | m_PrefabInstance: {fileID: 0}
323 | m_PrefabAsset: {fileID: 0}
324 | m_GameObject: {fileID: 797135392384398425}
325 | m_CullTransparentMesh: 1
326 | --- !u!114 &797135392384398427
327 | MonoBehaviour:
328 | m_ObjectHideFlags: 0
329 | m_CorrespondingSourceObject: {fileID: 0}
330 | m_PrefabInstance: {fileID: 0}
331 | m_PrefabAsset: {fileID: 0}
332 | m_GameObject: {fileID: 797135392384398425}
333 | m_Enabled: 1
334 | m_EditorHideFlags: 0
335 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
336 | m_Name:
337 | m_EditorClassIdentifier:
338 | m_Material: {fileID: 0}
339 | m_Color: {r: 0.6132076, g: 0.6132076, b: 0.6132076, a: 1}
340 | m_RaycastTarget: 1
341 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
342 | m_Maskable: 1
343 | m_OnCullStateChanged:
344 | m_PersistentCalls:
345 | m_Calls: []
346 | m_FontData:
347 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
348 | m_FontSize: 32
349 | m_FontStyle: 0
350 | m_BestFit: 1
351 | m_MinSize: 3
352 | m_MaxSize: 69
353 | m_Alignment: 4
354 | m_AlignByGeometry: 0
355 | m_RichText: 1
356 | m_HorizontalOverflow: 0
357 | m_VerticalOverflow: 0
358 | m_LineSpacing: 1
359 | m_Text: tap to close
360 | --- !u!1 &4513550131565409115
361 | GameObject:
362 | m_ObjectHideFlags: 0
363 | m_CorrespondingSourceObject: {fileID: 0}
364 | m_PrefabInstance: {fileID: 0}
365 | m_PrefabAsset: {fileID: 0}
366 | serializedVersion: 6
367 | m_Component:
368 | - component: {fileID: 8288541070304566713}
369 | - component: {fileID: 6911031135055652780}
370 | - component: {fileID: 2838536811186430450}
371 | - component: {fileID: 4513550131565409114}
372 | m_Layer: 5
373 | m_Name: Message Panel
374 | m_TagString: Untagged
375 | m_Icon: {fileID: 0}
376 | m_NavMeshLayer: 0
377 | m_StaticEditorFlags: 0
378 | m_IsActive: 0
379 | --- !u!224 &8288541070304566713
380 | RectTransform:
381 | m_ObjectHideFlags: 0
382 | m_CorrespondingSourceObject: {fileID: 0}
383 | m_PrefabInstance: {fileID: 0}
384 | m_PrefabAsset: {fileID: 0}
385 | m_GameObject: {fileID: 4513550131565409115}
386 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
387 | m_LocalPosition: {x: 0, y: 0, z: 0}
388 | m_LocalScale: {x: 1, y: 1, z: 1}
389 | m_Children:
390 | - {fileID: 3384476359650293983}
391 | - {fileID: 797135392384398424}
392 | m_Father: {fileID: 797135391978056255}
393 | m_RootOrder: 0
394 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
395 | m_AnchorMin: {x: 0, y: 0}
396 | m_AnchorMax: {x: 1, y: 1}
397 | m_AnchoredPosition: {x: 0, y: 0}
398 | m_SizeDelta: {x: 0, y: 0}
399 | m_Pivot: {x: 0.5, y: 0.5}
400 | --- !u!222 &6911031135055652780
401 | CanvasRenderer:
402 | m_ObjectHideFlags: 0
403 | m_CorrespondingSourceObject: {fileID: 0}
404 | m_PrefabInstance: {fileID: 0}
405 | m_PrefabAsset: {fileID: 0}
406 | m_GameObject: {fileID: 4513550131565409115}
407 | m_CullTransparentMesh: 0
408 | --- !u!114 &2838536811186430450
409 | MonoBehaviour:
410 | m_ObjectHideFlags: 0
411 | m_CorrespondingSourceObject: {fileID: 0}
412 | m_PrefabInstance: {fileID: 0}
413 | m_PrefabAsset: {fileID: 0}
414 | m_GameObject: {fileID: 4513550131565409115}
415 | m_Enabled: 1
416 | m_EditorHideFlags: 0
417 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
418 | m_Name:
419 | m_EditorClassIdentifier:
420 | m_Material: {fileID: 0}
421 | m_Color: {r: 0.017772457, g: 0, b: 0.16981131, a: 1}
422 | m_RaycastTarget: 1
423 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
424 | m_Maskable: 1
425 | m_OnCullStateChanged:
426 | m_PersistentCalls:
427 | m_Calls: []
428 | m_Sprite: {fileID: 0}
429 | m_Type: 1
430 | m_PreserveAspect: 0
431 | m_FillCenter: 1
432 | m_FillMethod: 4
433 | m_FillAmount: 1
434 | m_FillClockwise: 1
435 | m_FillOrigin: 0
436 | m_UseSpriteMesh: 0
437 | m_PixelsPerUnitMultiplier: 1
438 | --- !u!114 &4513550131565409114
439 | MonoBehaviour:
440 | m_ObjectHideFlags: 0
441 | m_CorrespondingSourceObject: {fileID: 0}
442 | m_PrefabInstance: {fileID: 0}
443 | m_PrefabAsset: {fileID: 0}
444 | m_GameObject: {fileID: 4513550131565409115}
445 | m_Enabled: 1
446 | m_EditorHideFlags: 0
447 | m_Script: {fileID: 11500000, guid: da6aecf803b15f446ad259836bb2cf4f, type: 3}
448 | m_Name:
449 | m_EditorClassIdentifier:
450 | messageLabel: {fileID: 4547605496105329212}
451 |
--------------------------------------------------------------------------------
/Runtime/Native/iOS/WebView.mm:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 Keijiro Takahashi
3 | * Copyright (C) 2012 GREE, Inc.
4 | *
5 | * This software is provided 'as-is', without any express or implied
6 | * warranty. In no event will the authors be held liable for any damages
7 | * arising from the use of this software.
8 | *
9 | * Permission is granted to anyone to use this software for any purpose,
10 | * including commercial applications, and to alter it and redistribute it
11 | * freely, subject to the following restrictions:
12 | *
13 | * 1. The origin of this software must not be misrepresented; you must not
14 | * claim that you wrote the original software. If you use this software
15 | * in a product, an acknowledgment in the product documentation would be
16 | * appreciated but is not required.
17 | * 2. Altered source versions must be plainly marked as such, and must not be
18 | * misrepresented as being the original software.
19 | * 3. This notice may not be removed or altered from any source distribution.
20 | */
21 |
22 | #if !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
23 |
24 | #import
25 | #import
26 |
27 | // NOTE: we need extern without "C" before unity 4.5
28 | //extern UIViewController *UnityGetGLViewController();
29 | extern "C" UIViewController *UnityGetGLViewController();
30 | extern "C" void UnitySendMessage(const char *, const char *, const char *);
31 |
32 | @protocol WebViewProtocol
33 | @property (nonatomic, getter=isOpaque) BOOL opaque;
34 | @property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
35 | @property (nonatomic, getter=isHidden) BOOL hidden;
36 | @property (nonatomic) CGRect frame;
37 | @property (nullable, nonatomic, weak) id navigationDelegate;
38 | @property (nullable, nonatomic, weak) id UIDelegate;
39 | @property (nullable, nonatomic, readonly, copy) NSURL *URL;
40 | - (void)load:(NSURLRequest *)request;
41 | - (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
42 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
43 | @property (nonatomic, readonly) BOOL canGoBack;
44 | @property (nonatomic, readonly) BOOL canGoForward;
45 | - (void)goBack;
46 | - (void)goForward;
47 | - (void)reload;
48 | - (void)stopLoading;
49 | - (void)setScrollBounce:(BOOL)enable;
50 | @end
51 |
52 | @interface WKWebView(WebViewProtocolConformed)
53 | @end
54 |
55 | @implementation WKWebView(WebViewProtocolConformed)
56 |
57 | - (void)load:(NSURLRequest *)request
58 | {
59 | WKWebView *webView = (WKWebView *)self;
60 | NSURL *url = [request URL];
61 | if ([url.absoluteString hasPrefix:@"file:"]) {
62 | NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
63 | [webView loadFileURL:url allowingReadAccessToURL:top];
64 | } else {
65 | [webView loadRequest:request];
66 | }
67 | }
68 |
69 | - (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
70 | {
71 | NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
72 | for (NSString *key in [headerDictionary allKeys]) {
73 | [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
74 | }
75 | return (NSURLRequest *)[convertedRequest copy];
76 | }
77 |
78 | - (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
79 | {
80 | WKWebView *webView = (WKWebView *)self;
81 | [webView loadHTMLString:html baseURL:baseUrl];
82 | }
83 |
84 | - (void)setScrollBounce:(BOOL)enable
85 | {
86 | WKWebView *webView = (WKWebView *)self;
87 | webView.scrollView.bounces = enable;
88 | }
89 |
90 | @end
91 |
92 | @interface CWebViewPlugin : NSObject
93 | {
94 | UIView *webView;
95 | NSString *gameObjectName;
96 | NSMutableDictionary *customRequestHeader;
97 | BOOL alertDialogEnabled;
98 | NSRegularExpression *allowRegex;
99 | NSRegularExpression *denyRegex;
100 | NSRegularExpression *hookRegex;
101 | NSString *basicAuthUserName;
102 | NSString *basicAuthPassword;
103 | }
104 | @end
105 |
106 | @implementation CWebViewPlugin
107 |
108 | static WKProcessPool *_sharedProcessPool;
109 | static NSMutableArray *_instances = [[NSMutableArray alloc] init];
110 |
111 | - (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode
112 | {
113 | self = [super init];
114 |
115 | gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
116 | customRequestHeader = [[NSMutableDictionary alloc] init];
117 | alertDialogEnabled = true;
118 | allowRegex = nil;
119 | denyRegex = nil;
120 | hookRegex = nil;
121 | basicAuthUserName = nil;
122 | basicAuthPassword = nil;
123 | UIView *view = UnityGetGLViewController().view;
124 | if (enableWKWebView && [WKWebView class]) {
125 | if (_sharedProcessPool == NULL) {
126 | _sharedProcessPool = [[WKProcessPool alloc] init];
127 | }
128 | WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
129 | WKUserContentController *controller = [[WKUserContentController alloc] init];
130 | [controller addScriptMessageHandler:self name:@"unityControl"];
131 | if (!zoom) {
132 | WKUserScript *script
133 | = [[WKUserScript alloc]
134 | initWithSource:@"\
135 | (function() { \
136 | var meta = document.querySelector('meta[name=viewport]'); \
137 | if (meta == null) { \
138 | meta = document.createElement('meta'); \
139 | meta.name = 'viewport'; \
140 | } \
141 | meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
142 | var head = document.getElementsByTagName('head')[0]; \
143 | head.appendChild(meta); \
144 | })(); \
145 | "
146 | injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
147 | forMainFrameOnly:YES];
148 | [controller addUserScript:script];
149 | }
150 | configuration.userContentController = controller;
151 | configuration.allowsInlineMediaPlayback = true;
152 | if (@available(iOS 10.0, *)) {
153 | configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
154 | } else {
155 | if (@available(iOS 9.0, *)) {
156 | configuration.requiresUserActionForMediaPlayback = NO;
157 | } else {
158 | configuration.mediaPlaybackRequiresUserAction = NO;
159 | }
160 | }
161 | configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
162 | configuration.processPool = _sharedProcessPool;
163 | if (@available(iOS 13.0, *)) {
164 | configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
165 | }
166 | webView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
167 | webView.UIDelegate = self;
168 | webView.navigationDelegate = self;
169 | if (ua != NULL && strcmp(ua, "") != 0) {
170 | ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
171 | }
172 | // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
173 | // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
174 | if (@available(iOS 11.0, *)) {
175 | ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
176 | } else {
177 | //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
178 | }
179 | } else {
180 | webView = nil;
181 | return self;
182 | }
183 | if (transparent) {
184 | webView.opaque = NO;
185 | webView.backgroundColor = [UIColor clearColor];
186 | }
187 | webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
188 | webView.hidden = YES;
189 |
190 | [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
191 |
192 | [view addSubview:webView];
193 |
194 | return self;
195 | }
196 |
197 | - (void)dispose
198 | {
199 | if (webView != nil) {
200 | UIView *webView0 = webView;
201 | webView = nil;
202 | if ([webView0 isKindOfClass:[WKWebView class]]) {
203 | webView0.UIDelegate = nil;
204 | webView0.navigationDelegate = nil;
205 | }
206 | [webView0 stopLoading];
207 | [webView0 removeFromSuperview];
208 | [webView0 removeObserver:self forKeyPath:@"loading"];
209 | }
210 | basicAuthPassword = nil;
211 | basicAuthUserName = nil;
212 | hookRegex = nil;
213 | denyRegex = nil;
214 | allowRegex = nil;
215 | customRequestHeader = nil;
216 | gameObjectName = nil;
217 | }
218 |
219 | + (void)clearCookies
220 | {
221 | // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
222 | NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
223 | NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
224 | NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
225 | [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
226 | [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
227 |
228 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
229 | if (cookieStorage == nil) {
230 | // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
231 | cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
232 | }
233 | [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
234 | [cookieStorage deleteCookie:cookie];
235 | }];
236 |
237 | NSOperatingSystemVersion version = { 9, 0, 0 };
238 | if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
239 | NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
240 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
241 | [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
242 | modifiedSince:date
243 | completionHandler:^{}];
244 | }
245 | }
246 |
247 | + saveCookies
248 | {
249 | // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
250 | _sharedProcessPool = [[WKProcessPool alloc] init];
251 | [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
252 | if ([obj->webView isKindOfClass:[WKWebView class]]) {
253 | WKWebView *webView = (WKWebView *)obj->webView;
254 | webView.configuration.processPool = _sharedProcessPool;
255 | }
256 | }];
257 | }
258 |
259 | + (const char *)getCookies:(const char *)url
260 | {
261 | // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
262 | _sharedProcessPool = [[WKProcessPool alloc] init];
263 | [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
264 | if ([obj->webView isKindOfClass:[WKWebView class]]) {
265 | WKWebView *webView = (WKWebView *)obj->webView;
266 | webView.configuration.processPool = _sharedProcessPool;
267 | }
268 | }];
269 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
270 | formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
271 | [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
272 | NSMutableString *result = [NSMutableString string];
273 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
274 | if (cookieStorage == nil) {
275 | // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
276 | cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
277 | }
278 | [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
279 | enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
280 | [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
281 | if ([cookie.domain length] > 0) {
282 | [result appendString:[NSString stringWithFormat:@"; "]];
283 | [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
284 | }
285 | if ([cookie.path length] > 0) {
286 | [result appendString:[NSString stringWithFormat:@"; "]];
287 | [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
288 | }
289 | if (cookie.expiresDate != nil) {
290 | [result appendString:[NSString stringWithFormat:@"; "]];
291 | [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
292 | }
293 | [result appendString:[NSString stringWithFormat:@"; "]];
294 | [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
295 | [result appendString:[NSString stringWithFormat:@"\n"]];
296 | }];
297 | const char *s = [result UTF8String];
298 | char *r = (char *)malloc(strlen(s) + 1);
299 | strcpy(r, s);
300 | return r;
301 | }
302 |
303 | - (void)userContentController:(WKUserContentController *)userContentController
304 | didReceiveScriptMessage:(WKScriptMessage *)message {
305 |
306 | // Log out the message received
307 | NSLog(@"Received event %@", message.body);
308 | UnitySendMessage([gameObjectName UTF8String], "CallFromJS",
309 | [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
310 |
311 | /*
312 | // Then pull something from the device using the message body
313 | NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
314 |
315 | // Execute some JavaScript using the result?
316 | NSString *exec_template = @"set_headline(\"received: %@\");";
317 | NSString *exec = [NSString stringWithFormat:exec_template, version];
318 | [webView evaluateJavaScript:exec completionHandler:nil];
319 | */
320 | }
321 |
322 | - (void)observeValueForKeyPath:(NSString *)keyPath
323 | ofObject:(id)object
324 | change:(NSDictionary *)change
325 | context:(void *)context {
326 | if (webView == nil)
327 | return;
328 |
329 | if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
330 | && [webView URL] != nil) {
331 | UnitySendMessage(
332 | [gameObjectName UTF8String],
333 | "CallOnLoaded",
334 | [[[webView URL] absoluteString] UTF8String]);
335 |
336 | }
337 | }
338 |
339 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
340 | {
341 | UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
342 | }
343 |
344 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
345 | {
346 | UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
347 | }
348 |
349 | - (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
350 | {
351 | // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
352 | if (!navigationAction.targetFrame.isMainFrame) {
353 | [wkWebView loadRequest:navigationAction.request];
354 | }
355 | return nil;
356 | }
357 |
358 | - (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
359 | {
360 | if (webView == nil) {
361 | decisionHandler(WKNavigationActionPolicyCancel);
362 | return;
363 | }
364 | NSURL *nsurl = [navigationAction.request URL];
365 | NSString *url = [nsurl absoluteString];
366 | BOOL pass = YES;
367 | if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
368 | pass = YES;
369 | } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
370 | pass = NO;
371 | }
372 | if (!pass) {
373 | decisionHandler(WKNavigationActionPolicyCancel);
374 | return;
375 | }
376 | if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
377 | [[UIApplication sharedApplication] openURL:nsurl];
378 | decisionHandler(WKNavigationActionPolicyCancel);
379 | return;
380 | } else if ([url hasPrefix:@"unity:"]) {
381 | UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
382 | decisionHandler(WKNavigationActionPolicyCancel);
383 | return;
384 | } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
385 | UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
386 | decisionHandler(WKNavigationActionPolicyCancel);
387 | return;
388 | } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
389 | && ![url hasPrefix:@"file:"]
390 | && ![url hasPrefix:@"http:"]
391 | && ![url hasPrefix:@"https:"]) {
392 | if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
393 | [[UIApplication sharedApplication] openURL:nsurl];
394 | }
395 | decisionHandler(WKNavigationActionPolicyCancel);
396 | return;
397 | } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
398 | && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
399 | // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
400 | [webView load:navigationAction.request];
401 | decisionHandler(WKNavigationActionPolicyCancel);
402 | return;
403 | } else {
404 | if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
405 | // If the custom header is not attached, give it and make a request again.
406 | if (![self isSetupedCustomHeader:[navigationAction request]]) {
407 | NSLog(@"navi ... %@", navigationAction);
408 | [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
409 | decisionHandler(WKNavigationActionPolicyCancel);
410 | return;
411 | }
412 | }
413 | }
414 | UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
415 | decisionHandler(WKNavigationActionPolicyAllow);
416 | }
417 |
418 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
419 |
420 | if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
421 |
422 | NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
423 | if (response.statusCode >= 400) {
424 | UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
425 | }
426 |
427 | }
428 | decisionHandler(WKNavigationResponsePolicyAllow);
429 | }
430 |
431 | // alert
432 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
433 | {
434 | if (!alertDialogEnabled) {
435 | completionHandler();
436 | return;
437 | }
438 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
439 | message:message
440 | preferredStyle:UIAlertControllerStyleAlert];
441 | [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
442 | style:UIAlertActionStyleCancel
443 | handler:^(UIAlertAction *action) {
444 | completionHandler();
445 | }]];
446 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
447 | }
448 |
449 | // confirm
450 | - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
451 | {
452 | if (!alertDialogEnabled) {
453 | completionHandler(NO);
454 | return;
455 | }
456 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
457 | message:message
458 | preferredStyle:UIAlertControllerStyleAlert];
459 | [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
460 | style:UIAlertActionStyleDefault
461 | handler:^(UIAlertAction *action) {
462 | completionHandler(YES);
463 | }]];
464 | [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
465 | style:UIAlertActionStyleCancel
466 | handler:^(UIAlertAction *action) {
467 | completionHandler(NO);
468 | }]];
469 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
470 | }
471 |
472 | // prompt
473 | - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
474 | {
475 | if (!alertDialogEnabled) {
476 | completionHandler(nil);
477 | return;
478 | }
479 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
480 | message:prompt
481 | preferredStyle:UIAlertControllerStyleAlert];
482 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
483 | textField.text = defaultText;
484 | }];
485 | [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
486 | style:UIAlertActionStyleDefault
487 | handler:^(UIAlertAction *action) {
488 | NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
489 | completionHandler(input);
490 | }]];
491 | [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
492 | style:UIAlertActionStyleCancel
493 | handler:^(UIAlertAction *action) {
494 | completionHandler(nil);
495 | }]];
496 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
497 | }
498 |
499 | - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
500 | {
501 | NSURLSessionAuthChallengeDisposition disposition;
502 | NSURLCredential *credential;
503 | if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
504 | disposition = NSURLSessionAuthChallengeUseCredential;
505 | credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
506 | } else {
507 | disposition = NSURLSessionAuthChallengePerformDefaultHandling;
508 | credential = nil;
509 | }
510 | completionHandler(disposition, credential);
511 | }
512 |
513 | - (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
514 | {
515 | // Check for additional custom header.
516 | for (NSString *key in [customRequestHeader allKeys])
517 | {
518 | if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
519 | return NO;
520 | }
521 | }
522 | return YES;
523 | }
524 |
525 | - (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
526 | {
527 | NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
528 | for (NSString *key in [customRequestHeader allKeys]) {
529 | [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
530 | }
531 | return (NSURLRequest *)[convertedRequest copy];
532 | }
533 |
534 | - (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
535 | {
536 | if (webView == nil)
537 | return;
538 | UIView *view = UnityGetGLViewController().view;
539 | CGRect frame = webView.frame;
540 | CGRect screen = view.bounds;
541 | if (relative) {
542 | frame.size.width = screen.size.width * (1.0f - left - right);
543 | frame.size.height = screen.size.height * (1.0f - top - bottom);
544 | frame.origin.x = screen.size.width * left;
545 | frame.origin.y = screen.size.height * top;
546 | } else {
547 | CGFloat scale = 1.0f / [self getScale:view];
548 | frame.size.width = screen.size.width - scale * (left + right) ;
549 | frame.size.height = screen.size.height - scale * (top + bottom) ;
550 | frame.origin.x = scale * left ;
551 | frame.origin.y = scale * top ;
552 | }
553 | webView.frame = frame;
554 | }
555 |
556 | - (CGFloat)getScale:(UIView *)view
557 | {
558 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
559 | return view.window.screen.nativeScale;
560 | return view.contentScaleFactor;
561 | }
562 |
563 | - (void)setVisibility:(BOOL)visibility
564 | {
565 | if (webView == nil)
566 | return;
567 | webView.hidden = visibility ? NO : YES;
568 | }
569 |
570 | - (void)setAlertDialogEnabled:(BOOL)enabled
571 | {
572 | alertDialogEnabled = enabled;
573 | }
574 |
575 | - (void)setScrollBounceEnabled:(BOOL)enabled
576 | {
577 | [webView setScrollBounce:enabled];
578 | }
579 |
580 | - (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
581 | {
582 | NSError *err = nil;
583 | NSRegularExpression *allow = nil;
584 | NSRegularExpression *deny = nil;
585 | NSRegularExpression *hook = nil;
586 | if (allowPattern == nil || *allowPattern == '\0') {
587 | allow = nil;
588 | } else {
589 | allow
590 | = [NSRegularExpression
591 | regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
592 | options:0
593 | error:&err];
594 | if (err != nil) {
595 | return NO;
596 | }
597 | }
598 | if (denyPattern == nil || *denyPattern == '\0') {
599 | deny = nil;
600 | } else {
601 | deny
602 | = [NSRegularExpression
603 | regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
604 | options:0
605 | error:&err];
606 | if (err != nil) {
607 | return NO;
608 | }
609 | }
610 | if (hookPattern == nil || *hookPattern == '\0') {
611 | hook = nil;
612 | } else {
613 | hook
614 | = [NSRegularExpression
615 | regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
616 | options:0
617 | error:&err];
618 | if (err != nil) {
619 | return NO;
620 | }
621 | }
622 | allowRegex = allow;
623 | denyRegex = deny;
624 | hookRegex = hook;
625 | return YES;
626 | }
627 |
628 | - (void)loadURL:(const char *)url
629 | {
630 | if (webView == nil)
631 | return;
632 | NSString *urlStr = [NSString stringWithUTF8String:url];
633 | NSURL *nsurl = [NSURL URLWithString:urlStr];
634 | NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
635 | [webView load:request];
636 | }
637 |
638 | - (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
639 | {
640 | if (webView == nil)
641 | return;
642 | NSString *htmlStr = [NSString stringWithUTF8String:html];
643 | NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
644 | NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
645 | [webView loadHTML:htmlStr baseURL:baseNSUrl];
646 | }
647 |
648 | - (void)evaluateJS:(const char *)js
649 | {
650 | if (webView == nil)
651 | return;
652 | NSString *jsStr = [NSString stringWithUTF8String:js];
653 | [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
654 | }
655 |
656 | - (int)progress
657 | {
658 | if (webView == nil)
659 | return 0;
660 | if ([webView isKindOfClass:[WKWebView class]]) {
661 | return (int)([(WKWebView *)webView estimatedProgress] * 100);
662 | } else {
663 | return 0;
664 | }
665 | }
666 |
667 | - (BOOL)canGoBack
668 | {
669 | if (webView == nil)
670 | return false;
671 | return [webView canGoBack];
672 | }
673 |
674 | - (BOOL)canGoForward
675 | {
676 | if (webView == nil)
677 | return false;
678 | return [webView canGoForward];
679 | }
680 |
681 | - (void)goBack
682 | {
683 | if (webView == nil)
684 | return;
685 | [webView goBack];
686 | }
687 |
688 | - (void)goForward
689 | {
690 | if (webView == nil)
691 | return;
692 | [webView goForward];
693 | }
694 |
695 | - (void)reload
696 | {
697 | if (webView == nil)
698 | return;
699 | [webView reload];
700 | }
701 |
702 | - (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
703 | {
704 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
705 | NSString *valueString = [NSString stringWithUTF8String:headerValue];
706 |
707 | [customRequestHeader setObject:valueString forKey:keyString];
708 | }
709 |
710 | - (void)removeCustomRequestHeader:(const char *)headerKey
711 | {
712 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
713 |
714 | if ([[customRequestHeader allKeys]containsObject:keyString]) {
715 | [customRequestHeader removeObjectForKey:keyString];
716 | }
717 | }
718 |
719 | - (void)clearCustomRequestHeader
720 | {
721 | [customRequestHeader removeAllObjects];
722 | }
723 |
724 | - (const char *)getCustomRequestHeaderValue:(const char *)headerKey
725 | {
726 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
727 | NSString *result = [customRequestHeader objectForKey:keyString];
728 | if (!result) {
729 | return NULL;
730 | }
731 |
732 | const char *s = [result UTF8String];
733 | char *r = (char *)malloc(strlen(s) + 1);
734 | strcpy(r, s);
735 | return r;
736 | }
737 |
738 | - (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
739 | {
740 | basicAuthUserName = [NSString stringWithUTF8String:userName];
741 | basicAuthPassword = [NSString stringWithUTF8String:password];
742 | }
743 |
744 | - (void)clearCache:(BOOL)includeDiskFiles
745 | {
746 | if (webView == nil)
747 | return;
748 | NSMutableSet *types = [NSMutableSet setWithArray:@[WKWebsiteDataTypeMemoryCache]];
749 | if (includeDiskFiles) {
750 | [types addObject:WKWebsiteDataTypeDiskCache];
751 | }
752 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
753 | [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:types modifiedSince:date completionHandler:^{}];
754 | }
755 | @end
756 |
757 | extern "C" {
758 | void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode);
759 | void _CWebViewPlugin_Destroy(void *instance);
760 | void _CWebViewPlugin_SetMargins(
761 | void *instance, float left, float top, float right, float bottom, BOOL relative);
762 | void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
763 | void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
764 | void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
765 | BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
766 | void _CWebViewPlugin_LoadURL(void *instance, const char *url);
767 | void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
768 | void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
769 | int _CWebViewPlugin_Progress(void *instance);
770 | BOOL _CWebViewPlugin_CanGoBack(void *instance);
771 | BOOL _CWebViewPlugin_CanGoForward(void *instance);
772 | void _CWebViewPlugin_GoBack(void *instance);
773 | void _CWebViewPlugin_GoForward(void *instance);
774 | void _CWebViewPlugin_Reload(void *instance);
775 | void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
776 | void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
777 | void _CWebViewPlugin_ClearCustomHeader(void *instance);
778 | void _CWebViewPlugin_ClearCookies();
779 | void _CWebViewPlugin_SaveCookies();
780 | const char *_CWebViewPlugin_GetCookies(const char *url);
781 | const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
782 | void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
783 | void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
784 | }
785 |
786 | void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode)
787 | {
788 | if (! (enableWKWebView && [WKWebView class]))
789 | return nil;
790 | WKContentMode wkContentMode = WKContentModeRecommended;
791 | switch (contentMode) {
792 | case 1:
793 | wkContentMode = WKContentModeMobile;
794 | break;
795 | case 2:
796 | wkContentMode = WKContentModeDesktop;
797 | break;
798 | default:
799 | wkContentMode = WKContentModeRecommended;
800 | break;
801 | }
802 | CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode];
803 | [_instances addObject:webViewPlugin];
804 | return (__bridge_retained void *)webViewPlugin;
805 | }
806 |
807 | void _CWebViewPlugin_Destroy(void *instance)
808 | {
809 | if (instance == NULL)
810 | return;
811 | CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
812 | [_instances removeObject:webViewPlugin];
813 | [webViewPlugin dispose];
814 | webViewPlugin = nil;
815 | }
816 |
817 | void _CWebViewPlugin_SetMargins(
818 | void *instance, float left, float top, float right, float bottom, BOOL relative)
819 | {
820 | if (instance == NULL)
821 | return;
822 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
823 | [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
824 | }
825 |
826 | void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
827 | {
828 | if (instance == NULL)
829 | return;
830 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
831 | [webViewPlugin setVisibility:visibility];
832 | }
833 |
834 | void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
835 | {
836 | if (instance == NULL)
837 | return;
838 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
839 | [webViewPlugin setAlertDialogEnabled:enabled];
840 | }
841 |
842 | void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
843 | {
844 | if (instance == NULL)
845 | return;
846 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
847 | [webViewPlugin setScrollBounceEnabled:enabled];
848 | }
849 |
850 | BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
851 | {
852 | if (instance == NULL)
853 | return NO;
854 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
855 | return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
856 | }
857 |
858 | void _CWebViewPlugin_LoadURL(void *instance, const char *url)
859 | {
860 | if (instance == NULL)
861 | return;
862 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
863 | [webViewPlugin loadURL:url];
864 | }
865 |
866 | void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
867 | {
868 | if (instance == NULL)
869 | return;
870 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
871 | [webViewPlugin loadHTML:html baseURL:baseUrl];
872 | }
873 |
874 | void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
875 | {
876 | if (instance == NULL)
877 | return;
878 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
879 | [webViewPlugin evaluateJS:js];
880 | }
881 |
882 | int _CWebViewPlugin_Progress(void *instance)
883 | {
884 | if (instance == NULL)
885 | return 0;
886 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
887 | return [webViewPlugin progress];
888 | }
889 |
890 | BOOL _CWebViewPlugin_CanGoBack(void *instance)
891 | {
892 | if (instance == NULL)
893 | return false;
894 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
895 | return [webViewPlugin canGoBack];
896 | }
897 |
898 | BOOL _CWebViewPlugin_CanGoForward(void *instance)
899 | {
900 | if (instance == NULL)
901 | return false;
902 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
903 | return [webViewPlugin canGoForward];
904 | }
905 |
906 | void _CWebViewPlugin_GoBack(void *instance)
907 | {
908 | if (instance == NULL)
909 | return;
910 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
911 | [webViewPlugin goBack];
912 | }
913 |
914 | void _CWebViewPlugin_GoForward(void *instance)
915 | {
916 | if (instance == NULL)
917 | return;
918 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
919 | [webViewPlugin goForward];
920 | }
921 |
922 | void _CWebViewPlugin_Reload(void *instance)
923 | {
924 | if (instance == NULL)
925 | return;
926 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
927 | [webViewPlugin reload];
928 | }
929 |
930 | void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
931 | {
932 | if (instance == NULL)
933 | return;
934 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
935 | [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
936 | }
937 |
938 | void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
939 | {
940 | if (instance == NULL)
941 | return;
942 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
943 | [webViewPlugin removeCustomRequestHeader:headerKey];
944 | }
945 |
946 | void _CWebViewPlugin_ClearCustomHeader(void *instance)
947 | {
948 | if (instance == NULL)
949 | return;
950 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
951 | [webViewPlugin clearCustomRequestHeader];
952 | }
953 |
954 | void _CWebViewPlugin_ClearCookies()
955 | {
956 | [CWebViewPlugin clearCookies];
957 | }
958 |
959 | void _CWebViewPlugin_SaveCookies()
960 | {
961 | [CWebViewPlugin saveCookies];
962 | }
963 |
964 | const char *_CWebViewPlugin_GetCookies(const char *url)
965 | {
966 | return [CWebViewPlugin getCookies:url];
967 | }
968 |
969 | const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
970 | {
971 | if (instance == NULL)
972 | return NULL;
973 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
974 | return [webViewPlugin getCustomRequestHeaderValue:headerKey];
975 | }
976 |
977 | void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
978 | {
979 | if (instance == NULL)
980 | return;
981 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
982 | [webViewPlugin setBasicAuthInfo:userName password:password];
983 | }
984 |
985 | void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
986 | {
987 | if (instance == NULL)
988 | return;
989 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
990 | [webViewPlugin clearCache:includeDiskFiles];
991 | }
992 |
993 | #endif // !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
994 |
--------------------------------------------------------------------------------
/Runtime/Native/iOS/WebViewWithUIWebView.mm:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 Keijiro Takahashi
3 | * Copyright (C) 2012 GREE, Inc.
4 | *
5 | * This software is provided 'as-is', without any express or implied
6 | * warranty. In no event will the authors be held liable for any damages
7 | * arising from the use of this software.
8 | *
9 | * Permission is granted to anyone to use this software for any purpose,
10 | * including commercial applications, and to alter it and redistribute it
11 | * freely, subject to the following restrictions:
12 | *
13 | * 1. The origin of this software must not be misrepresented; you must not
14 | * claim that you wrote the original software. If you use this software
15 | * in a product, an acknowledgment in the product documentation would be
16 | * appreciated but is not required.
17 | * 2. Altered source versions must be plainly marked as such, and must not be
18 | * misrepresented as being the original software.
19 | * 3. This notice may not be removed or altered from any source distribution.
20 | */
21 |
22 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
23 |
24 | #import
25 | #import
26 |
27 | // NOTE: we need extern without "C" before unity 4.5
28 | //extern UIViewController *UnityGetGLViewController();
29 | extern "C" UIViewController *UnityGetGLViewController();
30 | extern "C" void UnitySendMessage(const char *, const char *, const char *);
31 |
32 | @protocol WebViewProtocol
33 | @property (nonatomic, getter=isOpaque) BOOL opaque;
34 | @property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
35 | @property (nonatomic, getter=isHidden) BOOL hidden;
36 | @property (nonatomic) CGRect frame;
37 | @property (nullable, nonatomic, assign) id delegate;
38 | @property (nullable, nonatomic, weak) id navigationDelegate;
39 | @property (nullable, nonatomic, weak) id UIDelegate;
40 | @property (nullable, nonatomic, readonly, copy) NSURL *URL;
41 | - (void)load:(NSURLRequest *)request;
42 | - (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
43 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
44 | @property (nonatomic, readonly) BOOL canGoBack;
45 | @property (nonatomic, readonly) BOOL canGoForward;
46 | - (void)goBack;
47 | - (void)goForward;
48 | - (void)reload;
49 | - (void)stopLoading;
50 | - (void)setScrollBounce:(BOOL)enable;
51 | @end
52 |
53 | @interface WKWebView(WebViewProtocolConformed)
54 | @end
55 |
56 | @implementation WKWebView(WebViewProtocolConformed)
57 |
58 | @dynamic delegate;
59 |
60 | - (void)load:(NSURLRequest *)request
61 | {
62 | WKWebView *webView = (WKWebView *)self;
63 | NSURL *url = [request URL];
64 | if ([url.absoluteString hasPrefix:@"file:"]) {
65 | NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
66 | [webView loadFileURL:url allowingReadAccessToURL:top];
67 | } else {
68 | [webView loadRequest:request];
69 | }
70 | }
71 |
72 | - (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
73 | {
74 | NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
75 | for (NSString *key in [headerDictionary allKeys]) {
76 | [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
77 | }
78 | return (NSURLRequest *)[convertedRequest copy];
79 | }
80 |
81 | - (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
82 | {
83 | WKWebView *webView = (WKWebView *)self;
84 | [webView loadHTMLString:html baseURL:baseUrl];
85 | }
86 |
87 | - (void)setScrollBounce:(BOOL)enable
88 | {
89 | WKWebView *webView = (WKWebView *)self;
90 | webView.scrollView.bounces = enable;
91 | }
92 |
93 | @end
94 |
95 | @interface UIWebView(WebViewProtocolConformed)
96 | @end
97 |
98 | @implementation UIWebView(WebViewProtocolConformed)
99 |
100 | @dynamic navigationDelegate;
101 | @dynamic UIDelegate;
102 |
103 | - (NSURL *)URL
104 | {
105 | return [NSURL URLWithString:[self stringByEvaluatingJavaScriptFromString:@"document.URL"]];
106 | }
107 |
108 | - (void)load:(NSURLRequest *)request
109 | {
110 | UIWebView *webView = (UIWebView *)self;
111 | [webView loadRequest:request];
112 | }
113 |
114 | - (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
115 | {
116 | UIWebView *webView = (UIWebView *)self;
117 | [webView loadHTMLString:html baseURL:baseUrl];
118 | }
119 |
120 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler
121 | {
122 | NSString *result = [self stringByEvaluatingJavaScriptFromString:javaScriptString];
123 | if (completionHandler) {
124 | completionHandler(result, nil);
125 | }
126 | }
127 |
128 | - (void)setScrollBounce:(BOOL)enable
129 | {
130 | UIWebView *webView = (UIWebView *)self;
131 | webView.scrollView.bounces = enable;
132 | }
133 |
134 | @end
135 |
136 | @interface CWebViewPlugin : NSObject
137 | {
138 | UIView *webView;
139 | NSString *gameObjectName;
140 | NSMutableDictionary *customRequestHeader;
141 | BOOL alertDialogEnabled;
142 | NSRegularExpression *allowRegex;
143 | NSRegularExpression *denyRegex;
144 | NSRegularExpression *hookRegex;
145 | NSString *basicAuthUserName;
146 | NSString *basicAuthPassword;
147 | }
148 | @end
149 |
150 | @implementation CWebViewPlugin
151 |
152 | static WKProcessPool *_sharedProcessPool;
153 | static NSMutableArray *_instances = [[NSMutableArray alloc] init];
154 |
155 | - (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode
156 | {
157 | self = [super init];
158 |
159 | gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
160 | customRequestHeader = [[NSMutableDictionary alloc] init];
161 | alertDialogEnabled = true;
162 | allowRegex = nil;
163 | denyRegex = nil;
164 | hookRegex = nil;
165 | basicAuthUserName = nil;
166 | basicAuthPassword = nil;
167 | UIView *view = UnityGetGLViewController().view;
168 | if (enableWKWebView && [WKWebView class]) {
169 | if (_sharedProcessPool == NULL) {
170 | _sharedProcessPool = [[WKProcessPool alloc] init];
171 | }
172 | WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
173 | WKUserContentController *controller = [[WKUserContentController alloc] init];
174 | [controller addScriptMessageHandler:self name:@"unityControl"];
175 | if (!zoom) {
176 | WKUserScript *script
177 | = [[WKUserScript alloc]
178 | initWithSource:@"\
179 | (function() { \
180 | var meta = document.querySelector('meta[name=viewport]'); \
181 | if (meta == null) { \
182 | meta = document.createElement('meta'); \
183 | meta.name = 'viewport'; \
184 | } \
185 | meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
186 | var head = document.getElementsByTagName('head')[0]; \
187 | head.appendChild(meta); \
188 | })(); \
189 | "
190 | injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
191 | forMainFrameOnly:YES];
192 | [controller addUserScript:script];
193 | }
194 | configuration.userContentController = controller;
195 | configuration.allowsInlineMediaPlayback = true;
196 | if (@available(iOS 10.0, *)) {
197 | configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
198 | } else {
199 | if (@available(iOS 9.0, *)) {
200 | configuration.requiresUserActionForMediaPlayback = NO;
201 | } else {
202 | configuration.mediaPlaybackRequiresUserAction = NO;
203 | }
204 | }
205 | configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
206 | configuration.processPool = _sharedProcessPool;
207 | if (@available(iOS 13.0, *)) {
208 | configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
209 | }
210 | webView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
211 | webView.UIDelegate = self;
212 | webView.navigationDelegate = self;
213 | if (ua != NULL && strcmp(ua, "") != 0) {
214 | ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
215 | }
216 | // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
217 | // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
218 | if (@available(iOS 11.0, *)) {
219 | ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
220 | } else {
221 | //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
222 | }
223 | } else {
224 | if (ua != NULL && strcmp(ua, "") != 0) {
225 | [[NSUserDefaults standardUserDefaults]
226 | registerDefaults:@{ @"UserAgent": [[NSString alloc] initWithUTF8String:ua] }];
227 | }
228 | UIWebView *uiwebview = [[UIWebView alloc] initWithFrame:view.frame];
229 | uiwebview.allowsInlineMediaPlayback = YES;
230 | uiwebview.mediaPlaybackRequiresUserAction = NO;
231 | webView = uiwebview;
232 | webView.delegate = self;
233 | }
234 | if (transparent) {
235 | webView.opaque = NO;
236 | webView.backgroundColor = [UIColor clearColor];
237 | }
238 | webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
239 | webView.hidden = YES;
240 |
241 | [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
242 |
243 | [view addSubview:webView];
244 |
245 | return self;
246 | }
247 |
248 | - (void)dispose
249 | {
250 | if (webView != nil) {
251 | UIView *webView0 = webView;
252 | webView = nil;
253 | if ([webView0 isKindOfClass:[WKWebView class]]) {
254 | webView0.UIDelegate = nil;
255 | webView0.navigationDelegate = nil;
256 | } else {
257 | webView0.delegate = nil;
258 | }
259 | [webView0 stopLoading];
260 | [webView0 removeFromSuperview];
261 | [webView0 removeObserver:self forKeyPath:@"loading"];
262 | }
263 | basicAuthPassword = nil;
264 | basicAuthUserName = nil;
265 | hookRegex = nil;
266 | denyRegex = nil;
267 | allowRegex = nil;
268 | customRequestHeader = nil;
269 | gameObjectName = nil;
270 | }
271 |
272 | + (void)clearCookies
273 | {
274 | // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
275 | NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
276 | NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
277 | NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
278 | [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
279 | [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
280 |
281 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
282 | if (cookieStorage == nil) {
283 | // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
284 | cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
285 | }
286 | [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
287 | [cookieStorage deleteCookie:cookie];
288 | }];
289 |
290 | NSOperatingSystemVersion version = { 9, 0, 0 };
291 | if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
292 | NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
293 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
294 | [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
295 | modifiedSince:date
296 | completionHandler:^{}];
297 | }
298 | }
299 |
300 | + saveCookies
301 | {
302 | // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
303 | _sharedProcessPool = [[WKProcessPool alloc] init];
304 | [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
305 | if ([obj->webView isKindOfClass:[WKWebView class]]) {
306 | WKWebView *webView = (WKWebView *)obj->webView;
307 | webView.configuration.processPool = _sharedProcessPool;
308 | }
309 | }];
310 | }
311 |
312 | + (const char *)getCookies:(const char *)url
313 | {
314 | // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
315 | _sharedProcessPool = [[WKProcessPool alloc] init];
316 | [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
317 | if ([obj->webView isKindOfClass:[WKWebView class]]) {
318 | WKWebView *webView = (WKWebView *)obj->webView;
319 | webView.configuration.processPool = _sharedProcessPool;
320 | }
321 | }];
322 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
323 | formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
324 | [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
325 | NSMutableString *result = [NSMutableString string];
326 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
327 | if (cookieStorage == nil) {
328 | // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
329 | cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
330 | }
331 | [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
332 | enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
333 | [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
334 | if ([cookie.domain length] > 0) {
335 | [result appendString:[NSString stringWithFormat:@"; "]];
336 | [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
337 | }
338 | if ([cookie.path length] > 0) {
339 | [result appendString:[NSString stringWithFormat:@"; "]];
340 | [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
341 | }
342 | if (cookie.expiresDate != nil) {
343 | [result appendString:[NSString stringWithFormat:@"; "]];
344 | [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
345 | }
346 | [result appendString:[NSString stringWithFormat:@"; "]];
347 | [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
348 | [result appendString:[NSString stringWithFormat:@"\n"]];
349 | }];
350 | const char *s = [result UTF8String];
351 | char *r = (char *)malloc(strlen(s) + 1);
352 | strcpy(r, s);
353 | return r;
354 | }
355 |
356 | - (void)userContentController:(WKUserContentController *)userContentController
357 | didReceiveScriptMessage:(WKScriptMessage *)message {
358 |
359 | // Log out the message received
360 | NSLog(@"Received event %@", message.body);
361 | UnitySendMessage([gameObjectName UTF8String], "CallFromJS",
362 | [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
363 |
364 | /*
365 | // Then pull something from the device using the message body
366 | NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
367 |
368 | // Execute some JavaScript using the result?
369 | NSString *exec_template = @"set_headline(\"received: %@\");";
370 | NSString *exec = [NSString stringWithFormat:exec_template, version];
371 | [webView evaluateJavaScript:exec completionHandler:nil];
372 | */
373 | }
374 |
375 | - (void)observeValueForKeyPath:(NSString *)keyPath
376 | ofObject:(id)object
377 | change:(NSDictionary *)change
378 | context:(void *)context {
379 | if (webView == nil)
380 | return;
381 |
382 | if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
383 | && [webView URL] != nil) {
384 | UnitySendMessage(
385 | [gameObjectName UTF8String],
386 | "CallOnLoaded",
387 | [[[webView URL] absoluteString] UTF8String]);
388 |
389 | }
390 | }
391 |
392 | - (void)webView:(UIWebView *)uiWebView didFailLoadWithError:(NSError *)error
393 | {
394 | UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
395 | }
396 |
397 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
398 | {
399 | UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
400 | }
401 |
402 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
403 | {
404 | UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
405 | }
406 |
407 | - (void)webViewDidFinishLoad:(UIWebView *)uiWebView {
408 | if (webView == nil)
409 | return;
410 | // cf. http://stackoverflow.com/questions/10996028/uiwebview-when-did-a-page-really-finish-loading/15916853#15916853
411 | if ([[uiWebView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]
412 | && [webView URL] != nil) {
413 | UnitySendMessage(
414 | [gameObjectName UTF8String],
415 | "CallOnLoaded",
416 | [[[webView URL] absoluteString] UTF8String]);
417 | }
418 | }
419 |
420 | - (BOOL)webView:(UIWebView *)uiWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
421 | {
422 | if (webView == nil)
423 | return YES;
424 |
425 | NSURL *nsurl = [request URL];
426 | NSString *url = [nsurl absoluteString];
427 | BOOL pass = YES;
428 | if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
429 | pass = YES;
430 | } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
431 | pass = NO;
432 | }
433 | if (!pass) {
434 | return NO;
435 | }
436 | if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
437 | [[UIApplication sharedApplication] openURL:nsurl];
438 | return NO;
439 | } else if ([url hasPrefix:@"unity:"]) {
440 | UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
441 | return NO;
442 | } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
443 | UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
444 | return NO;
445 | } else {
446 | if (![self isSetupedCustomHeader:request]) {
447 | [uiWebView loadRequest:[self constructionCustomHeader:request]];
448 | return NO;
449 | }
450 | UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
451 | return YES;
452 | }
453 | }
454 |
455 | - (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
456 | {
457 | // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
458 | if (!navigationAction.targetFrame.isMainFrame) {
459 | [wkWebView loadRequest:navigationAction.request];
460 | }
461 | return nil;
462 | }
463 |
464 | - (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
465 | {
466 | if (webView == nil) {
467 | decisionHandler(WKNavigationActionPolicyCancel);
468 | return;
469 | }
470 | NSURL *nsurl = [navigationAction.request URL];
471 | NSString *url = [nsurl absoluteString];
472 | BOOL pass = YES;
473 | if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
474 | pass = YES;
475 | } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
476 | pass = NO;
477 | }
478 | if (!pass) {
479 | decisionHandler(WKNavigationActionPolicyCancel);
480 | return;
481 | }
482 | if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
483 | [[UIApplication sharedApplication] openURL:nsurl];
484 | decisionHandler(WKNavigationActionPolicyCancel);
485 | return;
486 | } else if ([url hasPrefix:@"unity:"]) {
487 | UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
488 | decisionHandler(WKNavigationActionPolicyCancel);
489 | return;
490 | } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
491 | UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
492 | decisionHandler(WKNavigationActionPolicyCancel);
493 | return;
494 | } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
495 | && ![url hasPrefix:@"file:"]
496 | && ![url hasPrefix:@"http:"]
497 | && ![url hasPrefix:@"https:"]) {
498 | if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
499 | [[UIApplication sharedApplication] openURL:nsurl];
500 | }
501 | decisionHandler(WKNavigationActionPolicyCancel);
502 | return;
503 | } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
504 | && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
505 | // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
506 | [webView load:navigationAction.request];
507 | decisionHandler(WKNavigationActionPolicyCancel);
508 | return;
509 | } else {
510 | if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
511 | // If the custom header is not attached, give it and make a request again.
512 | if (![self isSetupedCustomHeader:[navigationAction request]]) {
513 | NSLog(@"navi ... %@", navigationAction);
514 | [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
515 | decisionHandler(WKNavigationActionPolicyCancel);
516 | return;
517 | }
518 | }
519 | }
520 | UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
521 | decisionHandler(WKNavigationActionPolicyAllow);
522 | }
523 |
524 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
525 |
526 | if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
527 |
528 | NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
529 | if (response.statusCode >= 400) {
530 | UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
531 | }
532 |
533 | }
534 | decisionHandler(WKNavigationResponsePolicyAllow);
535 | }
536 |
537 | // alert
538 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
539 | {
540 | if (!alertDialogEnabled) {
541 | completionHandler();
542 | return;
543 | }
544 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
545 | message:message
546 | preferredStyle:UIAlertControllerStyleAlert];
547 | [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
548 | style:UIAlertActionStyleCancel
549 | handler:^(UIAlertAction *action) {
550 | completionHandler();
551 | }]];
552 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
553 | }
554 |
555 | // confirm
556 | - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
557 | {
558 | if (!alertDialogEnabled) {
559 | completionHandler(NO);
560 | return;
561 | }
562 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
563 | message:message
564 | preferredStyle:UIAlertControllerStyleAlert];
565 | [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
566 | style:UIAlertActionStyleDefault
567 | handler:^(UIAlertAction *action) {
568 | completionHandler(YES);
569 | }]];
570 | [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
571 | style:UIAlertActionStyleCancel
572 | handler:^(UIAlertAction *action) {
573 | completionHandler(NO);
574 | }]];
575 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
576 | }
577 |
578 | // prompt
579 | - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
580 | {
581 | if (!alertDialogEnabled) {
582 | completionHandler(nil);
583 | return;
584 | }
585 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
586 | message:prompt
587 | preferredStyle:UIAlertControllerStyleAlert];
588 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
589 | textField.text = defaultText;
590 | }];
591 | [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
592 | style:UIAlertActionStyleDefault
593 | handler:^(UIAlertAction *action) {
594 | NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
595 | completionHandler(input);
596 | }]];
597 | [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
598 | style:UIAlertActionStyleCancel
599 | handler:^(UIAlertAction *action) {
600 | completionHandler(nil);
601 | }]];
602 | [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
603 | }
604 |
605 | - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
606 | {
607 | NSURLSessionAuthChallengeDisposition disposition;
608 | NSURLCredential *credential;
609 | if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
610 | disposition = NSURLSessionAuthChallengeUseCredential;
611 | credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
612 | } else {
613 | disposition = NSURLSessionAuthChallengePerformDefaultHandling;
614 | credential = nil;
615 | }
616 | completionHandler(disposition, credential);
617 | }
618 |
619 | - (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
620 | {
621 | // Check for additional custom header.
622 | for (NSString *key in [customRequestHeader allKeys])
623 | {
624 | if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
625 | return NO;
626 | }
627 | }
628 | return YES;
629 | }
630 |
631 | - (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
632 | {
633 | NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
634 | for (NSString *key in [customRequestHeader allKeys]) {
635 | [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
636 | }
637 | return (NSURLRequest *)[convertedRequest copy];
638 | }
639 |
640 | - (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
641 | {
642 | if (webView == nil)
643 | return;
644 | UIView *view = UnityGetGLViewController().view;
645 | CGRect frame = webView.frame;
646 | CGRect screen = view.bounds;
647 | if (relative) {
648 | frame.size.width = screen.size.width * (1.0f - left - right);
649 | frame.size.height = screen.size.height * (1.0f - top - bottom);
650 | frame.origin.x = screen.size.width * left;
651 | frame.origin.y = screen.size.height * top;
652 | } else {
653 | CGFloat scale = 1.0f / [self getScale:view];
654 | frame.size.width = screen.size.width - scale * (left + right) ;
655 | frame.size.height = screen.size.height - scale * (top + bottom) ;
656 | frame.origin.x = scale * left ;
657 | frame.origin.y = scale * top ;
658 | }
659 | webView.frame = frame;
660 | }
661 |
662 | - (CGFloat)getScale:(UIView *)view
663 | {
664 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
665 | return view.window.screen.nativeScale;
666 | return view.contentScaleFactor;
667 | }
668 |
669 | - (void)setVisibility:(BOOL)visibility
670 | {
671 | if (webView == nil)
672 | return;
673 | webView.hidden = visibility ? NO : YES;
674 | }
675 |
676 | - (void)setAlertDialogEnabled:(BOOL)enabled
677 | {
678 | alertDialogEnabled = enabled;
679 | }
680 |
681 | - (void)setScrollBounceEnabled:(BOOL)enabled
682 | {
683 | [webView setScrollBounce:enabled];
684 | }
685 |
686 | - (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
687 | {
688 | NSError *err = nil;
689 | NSRegularExpression *allow = nil;
690 | NSRegularExpression *deny = nil;
691 | NSRegularExpression *hook = nil;
692 | if (allowPattern == nil || *allowPattern == '\0') {
693 | allow = nil;
694 | } else {
695 | allow
696 | = [NSRegularExpression
697 | regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
698 | options:0
699 | error:&err];
700 | if (err != nil) {
701 | return NO;
702 | }
703 | }
704 | if (denyPattern == nil || *denyPattern == '\0') {
705 | deny = nil;
706 | } else {
707 | deny
708 | = [NSRegularExpression
709 | regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
710 | options:0
711 | error:&err];
712 | if (err != nil) {
713 | return NO;
714 | }
715 | }
716 | if (hookPattern == nil || *hookPattern == '\0') {
717 | hook = nil;
718 | } else {
719 | hook
720 | = [NSRegularExpression
721 | regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
722 | options:0
723 | error:&err];
724 | if (err != nil) {
725 | return NO;
726 | }
727 | }
728 | allowRegex = allow;
729 | denyRegex = deny;
730 | hookRegex = hook;
731 | return YES;
732 | }
733 |
734 | - (void)loadURL:(const char *)url
735 | {
736 | if (webView == nil)
737 | return;
738 | NSString *urlStr = [NSString stringWithUTF8String:url];
739 | NSURL *nsurl = [NSURL URLWithString:urlStr];
740 | NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
741 | [webView load:request];
742 | }
743 |
744 | - (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
745 | {
746 | if (webView == nil)
747 | return;
748 | NSString *htmlStr = [NSString stringWithUTF8String:html];
749 | NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
750 | NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
751 | [webView loadHTML:htmlStr baseURL:baseNSUrl];
752 | }
753 |
754 | - (void)evaluateJS:(const char *)js
755 | {
756 | if (webView == nil)
757 | return;
758 | NSString *jsStr = [NSString stringWithUTF8String:js];
759 | [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
760 | }
761 |
762 | - (int)progress
763 | {
764 | if (webView == nil)
765 | return 0;
766 | if ([webView isKindOfClass:[WKWebView class]]) {
767 | return (int)([(WKWebView *)webView estimatedProgress] * 100);
768 | } else {
769 | return 0;
770 | }
771 | }
772 |
773 | - (BOOL)canGoBack
774 | {
775 | if (webView == nil)
776 | return false;
777 | return [webView canGoBack];
778 | }
779 |
780 | - (BOOL)canGoForward
781 | {
782 | if (webView == nil)
783 | return false;
784 | return [webView canGoForward];
785 | }
786 |
787 | - (void)goBack
788 | {
789 | if (webView == nil)
790 | return;
791 | [webView goBack];
792 | }
793 |
794 | - (void)goForward
795 | {
796 | if (webView == nil)
797 | return;
798 | [webView goForward];
799 | }
800 |
801 | - (void)reload
802 | {
803 | if (webView == nil)
804 | return;
805 | [webView reload];
806 | }
807 |
808 | - (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
809 | {
810 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
811 | NSString *valueString = [NSString stringWithUTF8String:headerValue];
812 |
813 | [customRequestHeader setObject:valueString forKey:keyString];
814 | }
815 |
816 | - (void)removeCustomRequestHeader:(const char *)headerKey
817 | {
818 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
819 |
820 | if ([[customRequestHeader allKeys]containsObject:keyString]) {
821 | [customRequestHeader removeObjectForKey:keyString];
822 | }
823 | }
824 |
825 | - (void)clearCustomRequestHeader
826 | {
827 | [customRequestHeader removeAllObjects];
828 | }
829 |
830 | - (const char *)getCustomRequestHeaderValue:(const char *)headerKey
831 | {
832 | NSString *keyString = [NSString stringWithUTF8String:headerKey];
833 | NSString *result = [customRequestHeader objectForKey:keyString];
834 | if (!result) {
835 | return NULL;
836 | }
837 |
838 | const char *s = [result UTF8String];
839 | char *r = (char *)malloc(strlen(s) + 1);
840 | strcpy(r, s);
841 | return r;
842 | }
843 |
844 | - (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
845 | {
846 | basicAuthUserName = [NSString stringWithUTF8String:userName];
847 | basicAuthPassword = [NSString stringWithUTF8String:password];
848 | }
849 | @end
850 |
851 | extern "C" {
852 | void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode);
853 | void _CWebViewPlugin_Destroy(void *instance);
854 | void _CWebViewPlugin_SetMargins(
855 | void *instance, float left, float top, float right, float bottom, BOOL relative);
856 | void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
857 | void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
858 | void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
859 | BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
860 | void _CWebViewPlugin_LoadURL(void *instance, const char *url);
861 | void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
862 | void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
863 | int _CWebViewPlugin_Progress(void *instance);
864 | BOOL _CWebViewPlugin_CanGoBack(void *instance);
865 | BOOL _CWebViewPlugin_CanGoForward(void *instance);
866 | void _CWebViewPlugin_GoBack(void *instance);
867 | void _CWebViewPlugin_GoForward(void *instance);
868 | void _CWebViewPlugin_Reload(void *instance);
869 | void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
870 | void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
871 | void _CWebViewPlugin_ClearCustomHeader(void *instance);
872 | void _CWebViewPlugin_ClearCookies();
873 | void _CWebViewPlugin_SaveCookies();
874 | const char *_CWebViewPlugin_GetCookies(const char *url);
875 | const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
876 | void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
877 | void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
878 | }
879 |
880 | void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode)
881 | {
882 | WKContentMode wkContentMode = WKContentModeRecommended;
883 | switch (contentMode) {
884 | case 1:
885 | wkContentMode = WKContentModeMobile;
886 | break;
887 | case 2:
888 | wkContentMode = WKContentModeDesktop;
889 | break;
890 | default:
891 | wkContentMode = WKContentModeRecommended;
892 | break;
893 | }
894 | CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode];
895 | [_instances addObject:webViewPlugin];
896 | return (__bridge_retained void *)webViewPlugin;
897 | }
898 |
899 | void _CWebViewPlugin_Destroy(void *instance)
900 | {
901 | if (instance == NULL)
902 | return;
903 | CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
904 | [_instances removeObject:webViewPlugin];
905 | [webViewPlugin dispose];
906 | webViewPlugin = nil;
907 | }
908 |
909 | void _CWebViewPlugin_SetMargins(
910 | void *instance, float left, float top, float right, float bottom, BOOL relative)
911 | {
912 | if (instance == NULL)
913 | return;
914 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
915 | [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
916 | }
917 |
918 | void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
919 | {
920 | if (instance == NULL)
921 | return;
922 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
923 | [webViewPlugin setVisibility:visibility];
924 | }
925 |
926 | void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
927 | {
928 | if (instance == NULL)
929 | return;
930 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
931 | [webViewPlugin setAlertDialogEnabled:enabled];
932 | }
933 |
934 | void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
935 | {
936 | if (instance == NULL)
937 | return;
938 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
939 | [webViewPlugin setScrollBounceEnabled:enabled];
940 | }
941 |
942 | BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
943 | {
944 | if (instance == NULL)
945 | return NO;
946 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
947 | return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
948 | }
949 |
950 | void _CWebViewPlugin_LoadURL(void *instance, const char *url)
951 | {
952 | if (instance == NULL)
953 | return;
954 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
955 | [webViewPlugin loadURL:url];
956 | }
957 |
958 | void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
959 | {
960 | if (instance == NULL)
961 | return;
962 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
963 | [webViewPlugin loadHTML:html baseURL:baseUrl];
964 | }
965 |
966 | void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
967 | {
968 | if (instance == NULL)
969 | return;
970 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
971 | [webViewPlugin evaluateJS:js];
972 | }
973 |
974 | int _CWebViewPlugin_Progress(void *instance)
975 | {
976 | if (instance == NULL)
977 | return 0;
978 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
979 | return [webViewPlugin progress];
980 | }
981 |
982 | BOOL _CWebViewPlugin_CanGoBack(void *instance)
983 | {
984 | if (instance == NULL)
985 | return false;
986 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
987 | return [webViewPlugin canGoBack];
988 | }
989 |
990 | BOOL _CWebViewPlugin_CanGoForward(void *instance)
991 | {
992 | if (instance == NULL)
993 | return false;
994 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
995 | return [webViewPlugin canGoForward];
996 | }
997 |
998 | void _CWebViewPlugin_GoBack(void *instance)
999 | {
1000 | if (instance == NULL)
1001 | return;
1002 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1003 | [webViewPlugin goBack];
1004 | }
1005 |
1006 | void _CWebViewPlugin_GoForward(void *instance)
1007 | {
1008 | if (instance == NULL)
1009 | return;
1010 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1011 | [webViewPlugin goForward];
1012 | }
1013 |
1014 | void _CWebViewPlugin_Reload(void *instance)
1015 | {
1016 | if (instance == NULL)
1017 | return;
1018 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1019 | [webViewPlugin reload];
1020 | }
1021 |
1022 | void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
1023 | {
1024 | if (instance == NULL)
1025 | return;
1026 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1027 | [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
1028 | }
1029 |
1030 | void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
1031 | {
1032 | if (instance == NULL)
1033 | return;
1034 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1035 | [webViewPlugin removeCustomRequestHeader:headerKey];
1036 | }
1037 |
1038 | void _CWebViewPlugin_ClearCustomHeader(void *instance)
1039 | {
1040 | if (instance == NULL)
1041 | return;
1042 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1043 | [webViewPlugin clearCustomRequestHeader];
1044 | }
1045 |
1046 | void _CWebViewPlugin_ClearCookies()
1047 | {
1048 | [CWebViewPlugin clearCookies];
1049 | }
1050 |
1051 | void _CWebViewPlugin_SaveCookies()
1052 | {
1053 | [CWebViewPlugin saveCookies];
1054 | }
1055 |
1056 | const char *_CWebViewPlugin_GetCookies(const char *url)
1057 | {
1058 | return [CWebViewPlugin getCookies:url];
1059 | }
1060 |
1061 | const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
1062 | {
1063 | if (instance == NULL)
1064 | return NULL;
1065 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1066 | return [webViewPlugin getCustomRequestHeaderValue:headerKey];
1067 | }
1068 |
1069 | void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
1070 | {
1071 | if (instance == NULL)
1072 | return;
1073 | CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
1074 | [webViewPlugin setBasicAuthInfo:userName password:password];
1075 | }
1076 |
1077 | void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
1078 | {
1079 | // no op
1080 | }
1081 |
1082 | #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
1083 |
--------------------------------------------------------------------------------