diff --git a/.gitignore b/.gitignore
index baff6671..ce678838 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,33 @@
Library
Temp
*.csproj
-*.sln
*.pidb
*.userprefs
**/*.xcodeproj/*
!**/*.xcodeproj/project.pbxproj
!**/*.xcworkspace/contents.xcworkspacedata
+
+# Ignore global sln files but track the plugin's sln
+*.sln
+!plugins/Windows/WebViewPlugin.sln
+plugins/Windows/.vs
+plugins/Windows/obj
+plugins/Windows/packages/
+
+# Ignore Visual Studio build artifacts
+plugins/Windows/bin/**/*.exp
+plugins/Windows/bin/**/*.lib
+plugins/Windows/bin/**/*.pdb
+plugins/Windows/bin/**/*.ilk
+plugins/Windows/WebViewPlugin/Release/
+plugins/Windows/WebViewPlugin/Debug/
+plugins/Windows/WebViewPlugin/x64/
+*.log
+*.tlog
+
+# User-specific files
+*.user
+*.vcxproj.user
+
+# Downloaded tools
+plugins/Windows/nuget.exe
diff --git a/README.md b/README.md
index ae6058c0..0d027f55 100644
--- a/README.md
+++ b/README.md
@@ -1,73 +1,102 @@
# unity-webview
-unity-webview is a plugin for Unity 5 that overlays WebView components
-on Unity view. It works on Android, iOS, Unity Web Player, and Mac
-(Windows is not supported for now).
+`unity-webview` is a Unity 5 (and newer) plugin that overlays WebView components on Unity's rendering view. It supports Android, iOS, Unity Web Player, Mac, and Windows (Editor and Standalone).
-unity-webview is derived from keijiro-san's
-https://github.com/keijiro/unity-webview-integration .
+This plugin is derived from [keijiro-san's Unity WebView Integration](https://github.com/keijiro/unity-webview-integration).
-## Sample Project
+**Note:** This plugin overlays native WebView/WKWebView views over Unity's rendering view and does not support these views in 3D. For alternative solutions, refer to [this issue comment](https://github.com/gree/unity-webview/issues/612#issuecomment-724541385).
-It is placed under `sample/`. You can open it and import the plugin as
-below:
+## Getting Started
-1. Open `sample/Assets/Sample.unity`.
-2. Open `dist/unity-webview.unitypackage` and import all files. It
- might be easier to extract `dist/unity-webview.zip` instead if
- you've imported unity-webview before.
+We recommend starting from the sample project (under the `sample/` directory of the repo) as everything is already preconfigured:
+1. Clone this repo.
+2. Open `sample/Assets/Sample.unity` to open the sample project in Unity. If you are using a newer version of Unity, you may see warnings about compatibility. In most cases, it is safe to click "Continue". You might also be warned about project issues; please continue, as the required packages will be imported.
+3. Double click on `dist/unity-webview.unitypackage` to open it in Unity and click on `import` to import the package to your Unity project (You can also use [Package Manager](#package-manager) if you prefer). If you've imported `unity-webview` before, it might be easier to extract `dist/unity-webview.zip`.
+4. Click on the `SampleWebView` gameobject and put the url that you want to open by default (if http, refer to [Uses Cleartext Traffic](#uses-cleartext-traffic)).
+5. Select the platform you want to export to.
+6. Build.
-## Common Notes
+If you want to make the webview fullscreen:
+1. Open `Assets/Scripts/SampleWebView.cs`
+2. Edit this line ```webViewObject.SetMargins(0, 0, 0, 0);```
-### UnityWebViewPostprocessBuild.cs
+If you want to make the webview transparent (the background is the Unity scene):
+1. Open `Assets/Scripts/SampleWebView.cs`
+2. Uncomment (or add in the webViewObject.Init function if it doesn't exist) this line ```transparent: true```
+3. Add and remove a comma according to the code
-`Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs` contains code for Android/iOS build, which
-may cause errors if you haven't installed components for these platforms. As discussed in
-https://github.com/gree/unity-webview/pull/397#issuecomment-464631894, I intentionally don't utilize
-`UNITY_IOS` and/or `UNITY_ANDROID`. If you haven't installed Android/iOS components, please comment
-out adequate parts in the script.
+**Note:** For Android, the current implementation uses Android Fragment to enable the file input field. This might cause new issues. If you don't need the file input field, you can use `dist/unity-webview-nofragment.unitypackage` or `dist/unity-webview-nofragment.zip`.
-## Platform Specific Notes
+## Package Manager
-### Mac (Editor)
+For Unity 2019.4 or later, import the plugin using the Package Manager by adding the following entry to your `Packages/manifest.json`:
+
+```json
+{
+ "dependencies": {
+ "net.gree.unity-webview": "https://github.com/gree/unity-webview.git?path=/dist/package"
+ }
+}
+```
+
+For the variant without Fragment:
+
+```json
+{
+ "dependencies": {
+ "net.gree.unity-webview": "https://github.com/gree/unity-webview.git?path=/dist/package-nofragment"
+ }
+}
+```
+
+**Note:** Importing with the Package Manager does not work well for WebGL. Refer to the instructions for `dist/unity-webview.unitypackage`.
+
+## General Notes
+
+If you start from the `sample` project, most of the time you just have to comment and uncomment to make the webview fit your needs.
+
+Please also note that the Init function of the `Assets/Plugins/WebViewObject.cs` has a lot of parameters and can be quite long.
-#### Auto Graphics API/Metal Editor Support
+If you have a blank screen, it is most likely that you used HTTP (instead of HTTPS) or self-signed certificates. In this case, please refer to [Uses Cleartext Traffic](#uses-cleartext-traffic).
-The current implementation reiles on several OpenGL APIs so you need to disable "Auto graphics API"
-and specify OpenGLCore as below.
+## Platform-Specific Notes
-
+### Windows (Editor and Standalone)
-If you work only in (recent) Unity Editor, you may just disable "Metal Editor Support" (cf. https://github.com/gree/unity-webview/issues/383 ).
+Windows support uses **Microsoft WebView2** (Edge Chromium). The WebView is rendered offscreen and displayed as a texture in the game view (same pattern as Mac).
-
+- **WebView2 Runtime:** The machine running the Unity Editor or a built Windows game must have the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) installed. It is included by default on Windows 11 and many recent Windows 10 builds; otherwise the user can install it from Microsoft.
+- **Building the plugin:** The Windows native plugin (DLL) is not prebuilt in the repository. To use WebView on Windows, build it from the `plugins/Windows` folder with Visual Studio. See [plugins/Windows/README.md](plugins/Windows/README.md) for requirements (WebView2 SDK, NuGet or manual SDK), build steps, and where to copy `WebView.dll` (e.g. `Assets/Plugins/x64/WebView.dll` or `Assets/Plugins/x86/WebView.dll`).
+- **Performance:** On Windows, the WebView image is captured with WebView2's CapturePreview, which is costly. The default `bitmapRefreshCycle` is set to **3** (refresh every 3rd frame) to keep FPS up; you can change it on the `WebViewObject` component (e.g. 1 = every frame, 5 = every 5th frame). Higher values improve FPS but make the web view update less often.
+- **Distribution:** When distributing your game, either rely on the user having WebView2 Runtime installed or bundle the [fixed version runtime](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution) as documented by Microsoft.
+
+### Mac (Editor)
+
+#### macOS Version
+
+The implementation uses [WKWebView’s `takeSnapshotWithConfiguration`](https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshotwithconfiguration) to capture an offscreen WebView image. macOS 10.13 (High Sierra) or later is required.
#### App Transport Security
-Since Unity 5.3.0, Unity.app is built with ATS (App Transport
-Security) enabled and non-secured connection (HTTP) is not
-permitted. If you want to open `http://foo/bar.html` with this plugin
-on Unity Mac Editor, you need to open
-`/Applications/Unity5.3.4p3/Unity.app/Contents/Info.plist` with a text
-editor and add the following,
+Since Unity 5.3.0, Unity.app is built with ATS (App Transport Security) enabled, which does not permit non-secured (HTTP) connections. To open HTTP URLs in the Unity Mac Editor, update `/Applications/Unity5.3.4p3/Unity.app/Contents/Info.plist` as follows:
```diff
---- Info.plist~ 2016-04-11 18:29:25.000000000 +0900
-+++ Info.plist 2016-04-15 16:17:28.000000000 +0900
+--- Info.plist~ 2016-04-11 18:29:25.000000000 +0900
++++ Info.plist 2016-04-15 16:17:28.000000000 +0900
@@ -57,5 +57,10 @@
- EditorApplicationPrincipalClass
- UnityBuildNumber
- b902ad490cea
-+ NSAppTransportSecurity
-+
-+ NSAllowsArbitraryLoads
-+
-+
+ EditorApplicationPrincipalClass
+ UnityBuildNumber
+ b902ad490cea
++ NSAppTransportSecurity
++
++ NSAllowsArbitraryLoads
++
++
```
-or invoke the following from your terminal,
+Alternatively, execute the following terminal command:
```bash
/usr/libexec/PlistBuddy -c "Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true" /Applications/Unity/Unity.app/Contents/Info.plist
@@ -78,108 +107,121 @@ or invoke the following from your terminal,
* https://github.com/gree/unity-webview/issues/64
* https://onevcat.zendesk.com/hc/en-us/articles/215527307-I-cannot-open-the-web-page-in-Unity-Editor-
-#### WebViewSeparated.bundle
+#### Separate Mode
-WebViewSeparated.bundle is a variation of WebView.bundle. It is based
-on https://github.com/gree/unity-webview/pull/161 . As noted in the
-pull-request, it shows a separate window and allows a developer to
-utilize the Safari debugger. For enabling it, please define
-`WEBVIEW_SEPARATED`.
+Specify `separated: true` to open WebView in a separate window:
+
+```csharp
+webViewObject = (new GameObject("WebViewObject")).AddComponent();
+webViewObject.Init(
+#if UNITY_EDITOR
+ separated: true
+#endif
+ ...);
+```
+
+This allows the use of the Safari debugger, based on [pull request #161](https://github.com/gree/unity-webview/pull/161).
### iOS
-#### enableWKWebView
+#### Enable WKWebView
-The implementation now supports WKWebView but it is disabled by
-default. For enabling it, please set enableWKWebView as below:
+WKWebView is supported but disabled by default. Enable it with:
```csharp
- webViewObject = (new GameObject("WebViewObject")).AddComponent();
- webViewObject.Init(
- ...
- enableWKWebView: true);
+webViewObject = (new GameObject("WebViewObject")).AddComponent();
+webViewObject.Init(
+ ...
+ enableWKWebView: true);
```
-(cf. https://github.com/gree/unity-webview/blob/de9a25c0ab0622b15c15ecbc0c7cd85858aa7745/sample/Assets/Scripts/SampleWebView.cs#L94)
+[cf.](https://github.com/gree/unity-webview/blob/de9a25c0ab0622b15c15ecbc0c7cd85858aa7745/sample/Assets/Scripts/SampleWebView.cs#L94)
-This flag have no effect on platforms without WKWebView (such as iOS7 and Android) and should always
-be set true for iOS9 or later (see the next section).
+This flag has no effect on platforms without WKWebView (e.g., iOS7 and Android) and should be set to `true` for iOS9 or later.
-#### WKWebView only implementation for iOS9 or later
+#### WKWebView Only Implementation for iOS9 or later
-Apple recently sends the following warning for an app submission,
+Apple now warns against using `UIWebView` APIs:
> ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use
> UIWebView APIs . See https://developer.apple.com/documentation/uikit/uiwebview for more
> information.
-so the current implementation for iOS (Assets/Plugins/iOS/WebView.mmWebView) has two variations, in
-which new one utilizes only WKWebView if iOS deployment target is iOS9 or later. Please modify
-the following part of WebView.mm if you want to change this behaviour.
+The plugin includes two variations: `Assets/Plugins/iOS/WebView.mm` and `Assets/Plugins/iOS/WebViewWithUIWebView.mm`. Use `WebView.mm` for iOS9 or later.
+Modify `#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0` in these files if needed.
-```c++
-...
+*NOTE: WKWebView is available since iOS8 but was largely changed in iOS9, so we use `__IPHONE_9_0` instead of `__IPHONE_8_0`*
+*NOTE: Several versions of Unity themselves also have the ITMS-90809 issue (cf. https://issuetracker.unity3d.com/issues/ios-apple-throws-deprecated-api-usage-warning-for-using-uiwebview-when-submitting-builds-to-the-app-store-connect ).*/
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+#### XMLHttpRequest for File URLs
-...
-```
+WKWebView doesn't allow access to file URLs with XMLHttpRequest. This limitation can be relaxed by `allowFileAccessFromFileURLs`/`allowUniversalAccessFromFileURLs` settings. However, those are private APIs, so are currently disabled by default. For enabling them, please define `UNITYWEBVIEW_IOS_ALLOW_FILE_URLS`.
-(Note: WKWebView is available since iOS8 but was largely changed in iOS9, so we use `___IPHONE_9_0`
-instead of `__IPHONE_8_0`)
+cf. https://github.com/gree/unity-webview/issues/785
+cf. https://github.com/gree/unity-webview/issues/224#issuecomment-640642516
### Android
-#### hardwareAccelerated
+#### File Input Field
-The main activity should have `android:hardwareAccelerated="true"`, otherwise a webview won't run
-smoothly. Depending on unity versions, we need to set it as below (basically this will be done by
-post-process build scripts).
+The Android implementation uses Android Fragment for file input fields since [a1a2a89](https://github.com/gree/unity-webview/commit/a1a2a89d2d0ced366faed9db308ccf4f689a7278) and may cause new issues that were not found before.
+If you don't need the file input field, you can install `dist/unity-webview-nofragment.unitypackage` or `dist/unity-webview-nofragment.zip` for selecting the variant without Fragment.
-##### Unity 2018.1 or newer
+To enable file input fields, set the following permissions:
-Based on the technique discussed in
-https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/ and https://github.com/Over17/UnityAndroidManifestCallback, `Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs` edit the manifest to set `android:hardwareAccelerated="true"`. Please note this works with the `gradle` (not `internal`) build setting.
+* `android.permission.READ_EXTERNAL_STORAGE`
+* `android.permission.WRITE_EXTERNAL_STORAGE`
+* `android.permission.CAMERA`
-##### Unity 2017.x - 2018.0
+Set `android.permission.WRITE_EXTERNAL_STORAGE` in `Player Settings/Other Settings/Write Permission` and `android.permission.CAMERA` by defining `UNITYWEBVIEW_ANDROID_ENABLE_CAMERA`. (cf. [Camera/Audio Permissions](#cameraaudio-permissions)).
-Unity forcibly set `android:hardwareAccelerated="false"` regardless of its setting in `Plugins/Android/AndroidManifest.xml`, as discussed in https://github.com/gree/unity-webview/issues/382 (see also https://github.com/gree/unity-webview/issues/342 and https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/ ), and there is no solution for automatically correcting this setting. Please export the project and manually correct `AndroidManifest.xml`.
+#### Hardware Acceleration
-##### Unity 5.x or older
+Ensure the main activity has `android:hardwareAccelerated="true"`:
-After the initial build, `Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs` will copy
-`Temp/StatingArea/AndroidManifest-main.xml` to
-`Assets/Plugins/Android/AndroidManifest.xml`, edit the latter to add
-`android:hardwareAccelerated="true"` to `
+
+
+
+
+
```
-For allowing microphone access (`navigator.mediaDevices.getUserMedia({ audio:true })`), please define `UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE` so that `Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs` adds the followings to `AndroidManifest.xml`.
+and call the following on runtime.
+
+```c#
+ webViewObject.SetCameraAccess(true);
+```
+
+To allow microphone access (`navigator.mediaDevices.getUserMedia({ audio:true })`), please define `UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE` so that `Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs` adds the following to `AndroidManifest.xml`,
```xml
+
+
```
-Details for each Unity version are the same as for hardwareAccelerated. Please also note that it is necessary to request permissions at runtime for Android API 23 or later as below:
+and call the following on runtime.
+
+```c#
+ webViewObject.SetMicrophoneAccess(true);
+```
+
+Details for each Unity version are the same as for hardware acceleration. Please also note that it is necessary to request permissions at runtime for Android API 23 or later as below:
```diff
diff --git a/sample/Assets/Scripts/SampleWebView.cs b/sample/Assets/Scripts/SampleWebView.cs
@@ -231,33 +273,41 @@ index a62c1ca..a5efe9f 100644
(cf. https://github.com/gree/unity-webview/issues/473#issuecomment-559412496)
(cf. https://docs.unity3d.com/Manual/android-RequestingPermissions.html)
-#### How to build WebViewPlugin.jar
+#### `navigator.onLine`
-The following steps are for Mac but you can follow similar ones for Windows.
+Enable `navigator.onLine` by defining `UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE`. The plugin will check `Application.internetReachability` and update WebView's `setNetworkAvailable()`.
-1. Place Unity 5.6.1f1 as `/Applications/Unity5.6.1f1` on osx or `\Program Files\Unity5.6.1f1\` on windows.
-2. Install [Android Studio](https://developer.android.com/studio/install).
-3. Open Android Studio and select "Configure/SDK Manager", select the followings with "Show Package Details",
- and click OK.
- * SDK Platforms
- * Android 6.0 (Marshmallow)
- * Android SDK Platform 23
- * SDK Tools
- * Android SDK Build Tools
- * 28.0.2
-4. Open Terminal.app and perform the followings. You should find
- `unity-webview/build/Packager/Assets/Plugins/Android/WebViewPlugin.jar` if successful.
+#### Margin Adjustment for Keyboard Popup
+
+This plugin adjusts the bottom margin temporarily when the keyboard pops up to keep the focused input field displayed. This adjustment is, however, disabled for some cases (non-fullscreen mode or both portrait/landscape are enabled) to avoid odd behaviours (cf. https://github.com/gree/unity-webview/pull/809). Please define `UNITYWEBVIEW_ANDROID_FORCE_MARGIN_ADJUSTMENT_FOR_KEYBOARD` to force the margin adjustment even for these cases.
+
+#### How to build WebViewPlugin-*.aar.tmpl
+
+UnityWebViewPostprocessBuild.cs will select one of WebViewPlugin-*.aar.tmpl depending on EditorUserSettings.development. You can build these files as below:
+
+1. Install Unity 2019.4.41f2 with Android Build Support by Unity Hub.
+ * Also install Unity 5.6.1f1 from https://unity.com/ja/releases/editor/whats-new/5.6.1 and specify `--zorderpatch` if you need to include CUnityPlayer and CUnityPlayerActivity (cf. https://github.com/gree/unity-webview/pull/155 ).
+2. Open Terminal (mac) or Git Bash (windows), `cd plugins/Android`, and invoke `./install.sh`.
+
+If successful, you should find `build/Packager/Assets/Plugins/Android/WebViewPlugin-*.aar.tmpl`. install.sh has the following options:
+
+```
+Usage: ./install.sh [OPTIONS]
+
+Options:
+
+ --nofragment build a nofragment variant.
+ --development build a development variant.
+ --zorderpatch build with the patch for 5.6.0 and 5.6.1 (except 5.6.1p4)
-```bash
-$ export ANDROID_HOME=~/Library/Android/sdk
-$ export PATH=$PATH:~/Library/Android/sdk/platform-tools/bin:~/Library/Android/sdk/tools:~/Library/Android/sdk/tools/bin
-$ cd unity-webview/plugins/Android
-$ ./install.sh
```
### WebGL
-After importing `dist/unity-webview.unitypackage` or `dist/unity-webview.zip`, please copy `WebGLTemplates/Default/TemplateData` from your Unity installation to `Assets/WebGLTemplates/unity-webivew`. If you utilize Unity 2019.4.13f1 for example,
+*NOTE: for Unity 2020.1.0f1 or newer, please use `unity-webview-2020` instead of `unity-webview` below.*
+
+After importing `dist/unity-webview.unitypackage` or `dist/unity-webview.zip`, please copy
+`WebGLTemplates/Default/TemplateData` from your Unity installation to `Assets/WebGLTemplates/unity-webview`. If you use Unity 2018.4.13f1 for example,
```bash
$ cp -a /Applications/Unity/Hub/Editor/2018.4.13f1/PlaybackEngines/WebGLSupport/BuildTools/WebGLTemplates/Default/TemplateData Assets/WebGLTemplates/unity-webview
@@ -265,11 +315,6 @@ $ cp -a /Applications/Unity/Hub/Editor/2018.4.13f1/PlaybackEngines/WebGLSupport/
Then in `Project Settings/Player/Resolution and Presentation`, please select `unity-webview` in `WebGL Template`.
-### Web Player
-
-*NOTE: Web Player is obsolete so that the support for it will be removed.*
+## Star History
-The implementation utilizes IFRAME so please put both
-"an\_unityplayer\_page.html" and "a\_page\_loaded\_in\_webview.html"
-should be placed on the same domain for avoiding cross-domain
-requests.
+]
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/.gitignore b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/.gitignore
new file mode 100644
index 00000000..72c27e4f
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/.gitignore
@@ -0,0 +1,71 @@
+# This .gitignore file should be placed at the root of your Unity project directory
+#
+# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
+#
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+
+# MemoryCaptures can get excessive in size.
+# They also could contain extremely sensitive data
+/[Mm]emoryCaptures/
+
+# Asset meta data should only be ignored when the corresponding asset is also ignored
+!/[Aa]ssets/**/*.meta
+
+# Uncomment this line if you wish to ignore the asset store tools plugin
+# /[Aa]ssets/AssetStoreTools*
+
+# Autogenerated Jetbrains Rider plugin
+/[Aa]ssets/Plugins/Editor/JetBrains*
+
+# Visual Studio cache directory
+.vs/
+
+# Gradle cache directory
+.gradle/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.mdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+*.mdb.meta
+
+# Unity3D generated file on crash reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.aab
+*.unitypackage
+
+# Crashlytics generated file
+crashlytics-build.properties
+
+# Packed Addressables
+/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
+
+# Temporary auto-generated Android Assets
+/[Aa]ssets/[Ss]treamingAssets/aa.meta
+/[Aa]ssets/[Ss]treamingAssets/aa/*
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs
new file mode 100644
index 00000000..4e204965
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs
@@ -0,0 +1,52 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using UnityEngine;
+
+// cf. https://qiita.com/lucifuges/items/b17d602417a9a249689f
+
+public class Main : MonoBehaviour
+{
+ // Start is called before the first frame update
+ void Start()
+ {
+
+ }
+
+ // Update is called once per frame
+ void Update()
+ {
+
+ }
+
+ void OnGUI()
+ {
+ if (GUI.Button(new Rect(10, 10, 150, 100), "Open")) {
+ LaunchURL("https://www.google.com");
+ }
+ }
+
+#if UNITY_EDITOR
+#elif UNITY_ANDROID
+#elif UNITY_IOS
+ [DllImport("__Internal")]
+ static extern void launchURL(string url);
+#endif
+
+ void LaunchURL(string url)
+ {
+#if UNITY_EDITOR
+ Application.OpenURL(url);
+#elif UNITY_ANDROID
+ using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityPlayer.GetStatic("currentActivity"))
+ using (var intentBuilder = new AndroidJavaObject("android.support.customtabs.CustomTabsIntent$Builder"))
+ using (var intent = intentBuilder.Call("build"))
+ using (var uriClass = new AndroidJavaClass("android.net.Uri"))
+ using (var uri = uriClass.CallStatic("parse", url))
+ intent.Call("launchUrl", activity, uri);
+#elif UNITY_IOS
+ launchURL(url);
+#endif
+ }
+}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs.meta
new file mode 100644
index 00000000..407e554c
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Main.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ad288d988aec04798b370acfd4bdd21e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins.meta
new file mode 100644
index 00000000..cf13b7b1
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c9e30acb017844291b95c386d9c63a0e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android.meta
new file mode 100644
index 00000000..2633646d
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cd3cbab1af89d4563a714d9236d04138
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar
new file mode 100644
index 00000000..f233d259
Binary files /dev/null and b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar differ
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar.meta
new file mode 100644
index 00000000..5c411503
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar.meta
@@ -0,0 +1,34 @@
+fileFormatVersion: 2
+guid: 78d1747c224aa4da6bbda943b0a7afa2
+labels:
+- gpsr
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Android: Android
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar
new file mode 100644
index 00000000..4919a6bd
Binary files /dev/null and b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar differ
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar.meta
new file mode 100644
index 00000000..5a3a2f79
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar.meta
@@ -0,0 +1,34 @@
+fileFormatVersion: 2
+guid: ed60d3aa167e249fd9f5af76fab03bc2
+labels:
+- gpsr
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Android: Android
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor.meta
new file mode 100644
index 00000000..3c9d2248
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e96e8afec47dd4d598e61b91b1c1632f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml
new file mode 100644
index 00000000..efc1866e
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml.meta
new file mode 100644
index 00000000..23b5b451
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/Editor/Dependencies.xml.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e541f418ea6c444f49eb45cd89eaf46a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS.meta
new file mode 100644
index 00000000..5c1563ed
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a1ce05261902b4536a64eedc87ee6d8d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm
new file mode 100644
index 00000000..4fb0c500
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm
@@ -0,0 +1,14 @@
+#import
+
+extern UIViewController* UnityGetGLViewController();
+
+extern "C"
+{
+ void launchURL(const char *url)
+ {
+ UIViewController *uvc = UnityGetGLViewController();
+ NSURL *URL = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ SFSafariViewController *sfvc = [[SFSafariViewController alloc] initWithURL:URL];
+ [uvc presentViewController:sfvc animated:YES completion:nil];
+ }
+}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm.meta
new file mode 100644
index 00000000..3bff3178
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Plugins/iOS/SafariView.mm.meta
@@ -0,0 +1,81 @@
+fileFormatVersion: 2
+guid: 3998dc8c6941744fa86b3618720c28ba
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 1
+ Exclude Linux64: 1
+ Exclude OSXUniversal: 1
+ Exclude WebGL: 1
+ Exclude Win: 1
+ Exclude Win64: 1
+ Exclude iOS: 0
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win
+ second:
+ enabled: 0
+ settings:
+ CPU: x86
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 0
+ settings:
+ CPU: x86_64
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ CPU: AnyCPU
+ CompileFlags:
+ FrameworkDependencies: SafariServices;
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes.meta
new file mode 100644
index 00000000..caee3078
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6aa978d467bc84e6aadcd71ea7ebe442
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity
new file mode 100644
index 00000000..64d2f495
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity
@@ -0,0 +1,314 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 9
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 705507994}
+ m_IndirectSpecularColor: {r: 0.4465934, g: 0.49642956, b: 0.5748249, a: 1}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 12
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 500
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 500
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 2
+ m_PVRDenoiserTypeDirect: 0
+ m_PVRDenoiserTypeIndirect: 0
+ m_PVRDenoiserTypeAO: 0
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 0
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &705507993
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 705507995}
+ - component: {fileID: 705507994}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &705507994
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 705507993}
+ m_Enabled: 1
+ serializedVersion: 10
+ m_Type: 1
+ m_Shape: 0
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.802082
+ m_CookieSize: 10
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 1
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ShadowRadius: 0
+ m_ShadowAngle: 0
+--- !u!4 &705507995
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 705507993}
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!1 &963194225
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 963194228}
+ - component: {fileID: 963194227}
+ - component: {fileID: 963194226}
+ - component: {fileID: 963194229}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &963194226
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963194225}
+ m_Enabled: 1
+--- !u!20 &963194227
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963194225}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &963194228
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963194225}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &963194229
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963194225}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ad288d988aec04798b370acfd4bdd21e, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity.meta b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity.meta
new file mode 100644
index 00000000..952bd1e9
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Assets/Scenes/SampleScene.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9fc0d4010bbf28b4594072e72b8655ab
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/com.google.external-dependency-manager-1.2.165.tgz b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/com.google.external-dependency-manager-1.2.165.tgz
new file mode 100644
index 00000000..56117bdb
Binary files /dev/null and b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/com.google.external-dependency-manager-1.2.165.tgz differ
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/manifest.json b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/manifest.json
new file mode 100644
index 00000000..965c3a74
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/manifest.json
@@ -0,0 +1,44 @@
+{
+ "dependencies": {
+ "com.google.external-dependency-manager": "file:com.google.external-dependency-manager-1.2.165.tgz",
+ "com.unity.collab-proxy": "1.3.9",
+ "com.unity.ide.rider": "2.0.7",
+ "com.unity.ide.visualstudio": "2.0.7",
+ "com.unity.ide.vscode": "1.2.3",
+ "com.unity.test-framework": "1.1.24",
+ "com.unity.textmeshpro": "3.0.4",
+ "com.unity.timeline": "1.4.7",
+ "com.unity.ugui": "1.0.0",
+ "com.unity.modules.ai": "1.0.0",
+ "com.unity.modules.androidjni": "1.0.0",
+ "com.unity.modules.animation": "1.0.0",
+ "com.unity.modules.assetbundle": "1.0.0",
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.cloth": "1.0.0",
+ "com.unity.modules.director": "1.0.0",
+ "com.unity.modules.imageconversion": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.particlesystem": "1.0.0",
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.physics2d": "1.0.0",
+ "com.unity.modules.screencapture": "1.0.0",
+ "com.unity.modules.terrain": "1.0.0",
+ "com.unity.modules.terrainphysics": "1.0.0",
+ "com.unity.modules.tilemap": "1.0.0",
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.uielements": "1.0.0",
+ "com.unity.modules.umbra": "1.0.0",
+ "com.unity.modules.unityanalytics": "1.0.0",
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
+ "com.unity.modules.unitywebrequestaudio": "1.0.0",
+ "com.unity.modules.unitywebrequesttexture": "1.0.0",
+ "com.unity.modules.unitywebrequestwww": "1.0.0",
+ "com.unity.modules.vehicles": "1.0.0",
+ "com.unity.modules.video": "1.0.0",
+ "com.unity.modules.vr": "1.0.0",
+ "com.unity.modules.wind": "1.0.0",
+ "com.unity.modules.xr": "1.0.0"
+ }
+}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/packages-lock.json b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/packages-lock.json
new file mode 100644
index 00000000..5aacc11e
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/Packages/packages-lock.json
@@ -0,0 +1,344 @@
+{
+ "dependencies": {
+ "com.google.external-dependency-manager": {
+ "version": "file:com.google.external-dependency-manager-1.2.165.tgz",
+ "depth": 0,
+ "source": "local-tarball",
+ "dependencies": {}
+ },
+ "com.unity.collab-proxy": {
+ "version": "1.3.9",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.ext.nunit": {
+ "version": "1.0.6",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.ide.rider": {
+ "version": "2.0.7",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.test-framework": "1.1.1"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.ide.visualstudio": {
+ "version": "2.0.7",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.test-framework": "1.1.9"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.ide.vscode": {
+ "version": "1.2.3",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.test-framework": {
+ "version": "1.1.24",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.ext.nunit": "1.0.6",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.textmeshpro": {
+ "version": "3.0.4",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.ugui": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.timeline": {
+ "version": "1.4.7",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.modules.director": "1.0.0",
+ "com.unity.modules.animation": "1.0.0",
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.particlesystem": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.ugui": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0"
+ }
+ },
+ "com.unity.modules.ai": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.androidjni": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.animation": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.assetbundle": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.audio": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.cloth": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0"
+ }
+ },
+ "com.unity.modules.director": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.animation": "1.0.0"
+ }
+ },
+ "com.unity.modules.imageconversion": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.imgui": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.jsonserialize": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.particlesystem": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.physics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.physics2d": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.screencapture": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.subsystems": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.terrain": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.terrainphysics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.terrain": "1.0.0"
+ }
+ },
+ "com.unity.modules.tilemap": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics2d": "1.0.0"
+ }
+ },
+ "com.unity.modules.ui": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.uielements": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.uielementsnative": "1.0.0"
+ }
+ },
+ "com.unity.modules.uielementsnative": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.umbra": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.unityanalytics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequest": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.unitywebrequestassetbundle": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.assetbundle": "1.0.0",
+ "com.unity.modules.unitywebrequest": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequestaudio": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.audio": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequesttexture": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequestwww": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
+ "com.unity.modules.unitywebrequestaudio": "1.0.0",
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.assetbundle": "1.0.0",
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.vehicles": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0"
+ }
+ },
+ "com.unity.modules.video": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.unitywebrequest": "1.0.0"
+ }
+ },
+ "com.unity.modules.vr": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.xr": "1.0.0"
+ }
+ },
+ "com.unity.modules.wind": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.xr": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.subsystems": "1.0.0"
+ }
+ }
+ }
+}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AndroidResolverDependencies.xml b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AndroidResolverDependencies.xml
new file mode 100644
index 00000000..bb0cdc11
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AndroidResolverDependencies.xml
@@ -0,0 +1,24 @@
+
+
+ com.android.support:customtabs:23.0.0
+
+
+ Assets/Plugins/Android/com.android.support.customtabs-23.0.0.aar
+ Assets/Plugins/Android/com.android.support.support-annotations-23.0.0.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AudioManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AudioManager.asset
new file mode 100644
index 00000000..07ebfb05
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/AudioManager.asset
@@ -0,0 +1,19 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!11 &1
+AudioManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Volume: 1
+ Rolloff Scale: 1
+ Doppler Factor: 1
+ Default Speaker Mode: 2
+ m_SampleRate: 0
+ m_DSPBufferSize: 1024
+ m_VirtualVoiceCount: 512
+ m_RealVoiceCount: 32
+ m_SpatializerPlugin:
+ m_AmbisonicDecoderPlugin:
+ m_DisableAudio: 0
+ m_VirtualizeEffects: 1
+ m_RequestedDSPBufferSize: 1024
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ClusterInputManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ClusterInputManager.asset
new file mode 100644
index 00000000..e7886b26
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ClusterInputManager.asset
@@ -0,0 +1,6 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!236 &1
+ClusterInputManager:
+ m_ObjectHideFlags: 0
+ m_Inputs: []
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/DynamicsManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/DynamicsManager.asset
new file mode 100644
index 00000000..cdc1f3ea
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/DynamicsManager.asset
@@ -0,0 +1,34 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!55 &1
+PhysicsManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 11
+ m_Gravity: {x: 0, y: -9.81, z: 0}
+ m_DefaultMaterial: {fileID: 0}
+ m_BounceThreshold: 2
+ m_SleepThreshold: 0.005
+ m_DefaultContactOffset: 0.01
+ m_DefaultSolverIterations: 6
+ m_DefaultSolverVelocityIterations: 1
+ m_QueriesHitBackfaces: 0
+ m_QueriesHitTriggers: 1
+ m_EnableAdaptiveForce: 0
+ m_ClothInterCollisionDistance: 0
+ m_ClothInterCollisionStiffness: 0
+ m_ContactsGeneration: 1
+ m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ m_AutoSimulation: 1
+ m_AutoSyncTransforms: 0
+ m_ReuseCollisionCallbacks: 1
+ m_ClothInterCollisionSettingsToggle: 0
+ m_ContactPairsMode: 0
+ m_BroadphaseType: 0
+ m_WorldBounds:
+ m_Center: {x: 0, y: 0, z: 0}
+ m_Extent: {x: 250, y: 250, z: 250}
+ m_WorldSubdivisions: 8
+ m_FrictionType: 0
+ m_EnableEnhancedDeterminism: 0
+ m_EnableUnifiedHeightmaps: 1
+ m_DefaultMaxAngluarSpeed: 7
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorBuildSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorBuildSettings.asset
new file mode 100644
index 00000000..0147887e
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorBuildSettings.asset
@@ -0,0 +1,8 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1045 &1
+EditorBuildSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Scenes: []
+ m_configObjects: {}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorSettings.asset
new file mode 100644
index 00000000..de5d0b2d
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/EditorSettings.asset
@@ -0,0 +1,30 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!159 &1
+EditorSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 11
+ m_ExternalVersionControlSupport: Visible Meta Files
+ m_SerializationMode: 2
+ m_LineEndingsForNewScripts: 0
+ m_DefaultBehaviorMode: 0
+ m_PrefabRegularEnvironment: {fileID: 0}
+ m_PrefabUIEnvironment: {fileID: 0}
+ m_SpritePackerMode: 0
+ m_SpritePackerPaddingPower: 1
+ m_EtcTextureCompressorBehavior: 1
+ m_EtcTextureFastCompressor: 1
+ m_EtcTextureNormalCompressor: 2
+ m_EtcTextureBestCompressor: 4
+ m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref
+ m_ProjectGenerationRootNamespace:
+ m_CollabEditorSettings:
+ inProgressEnabled: 1
+ m_EnableTextureStreamingInEditMode: 1
+ m_EnableTextureStreamingInPlayMode: 1
+ m_AsyncShaderCompilation: 1
+ m_EnterPlayModeOptionsEnabled: 0
+ m_EnterPlayModeOptions: 3
+ m_ShowLightmapResolutionOverlay: 1
+ m_UseLegacyProbeSampleCount: 0
+ m_SerializeInlineMappingsOnOneLine: 1
\ No newline at end of file
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GraphicsSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GraphicsSettings.asset
new file mode 100644
index 00000000..43369e3c
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GraphicsSettings.asset
@@ -0,0 +1,63 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!30 &1
+GraphicsSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 13
+ m_Deferred:
+ m_Mode: 1
+ m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
+ m_DeferredReflections:
+ m_Mode: 1
+ m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
+ m_ScreenSpaceShadows:
+ m_Mode: 1
+ m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
+ m_LegacyDeferred:
+ m_Mode: 1
+ m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
+ m_DepthNormals:
+ m_Mode: 1
+ m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
+ m_MotionVectors:
+ m_Mode: 1
+ m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
+ m_LightHalo:
+ m_Mode: 1
+ m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
+ m_LensFlare:
+ m_Mode: 1
+ m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
+ m_AlwaysIncludedShaders:
+ - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
+ m_PreloadedShaders: []
+ m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
+ type: 0}
+ m_CustomRenderPipeline: {fileID: 0}
+ m_TransparencySortMode: 0
+ m_TransparencySortAxis: {x: 0, y: 0, z: 1}
+ m_DefaultRenderingPath: 1
+ m_DefaultMobileRenderingPath: 1
+ m_TierSettings: []
+ m_LightmapStripping: 0
+ m_FogStripping: 0
+ m_InstancingStripping: 0
+ m_LightmapKeepPlain: 1
+ m_LightmapKeepDirCombined: 1
+ m_LightmapKeepDynamicPlain: 1
+ m_LightmapKeepDynamicDirCombined: 1
+ m_LightmapKeepShadowMask: 1
+ m_LightmapKeepSubtractive: 1
+ m_FogKeepLinear: 1
+ m_FogKeepExp: 1
+ m_FogKeepExp2: 1
+ m_AlbedoSwatchInfos: []
+ m_LightsUseLinearIntensity: 0
+ m_LightsUseColorTemperature: 0
+ m_LogWhenShaderIsCompiled: 0
+ m_AllowEnlightenSupportForUpgradedProject: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GvhProjectSettings.xml b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GvhProjectSettings.xml
new file mode 100644
index 00000000..f3dcefd0
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/GvhProjectSettings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/InputManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/InputManager.asset
new file mode 100644
index 00000000..17c8f538
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/InputManager.asset
@@ -0,0 +1,295 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!13 &1
+InputManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Axes:
+ - serializedVersion: 3
+ m_Name: Horizontal
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton: left
+ positiveButton: right
+ altNegativeButton: a
+ altPositiveButton: d
+ gravity: 3
+ dead: 0.001
+ sensitivity: 3
+ snap: 1
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Vertical
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton: down
+ positiveButton: up
+ altNegativeButton: s
+ altPositiveButton: w
+ gravity: 3
+ dead: 0.001
+ sensitivity: 3
+ snap: 1
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire1
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left ctrl
+ altNegativeButton:
+ altPositiveButton: mouse 0
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire2
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left alt
+ altNegativeButton:
+ altPositiveButton: mouse 1
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire3
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left shift
+ altNegativeButton:
+ altPositiveButton: mouse 2
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Jump
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: space
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse X
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse Y
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 1
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse ScrollWheel
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 2
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Horizontal
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0.19
+ sensitivity: 1
+ snap: 0
+ invert: 0
+ type: 2
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Vertical
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0.19
+ sensitivity: 1
+ snap: 0
+ invert: 1
+ type: 2
+ axis: 1
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire1
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 0
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire2
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 1
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire3
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 2
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Jump
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 3
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Submit
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: return
+ altNegativeButton:
+ altPositiveButton: joystick button 0
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Submit
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: enter
+ altNegativeButton:
+ altPositiveButton: space
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Cancel
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: escape
+ altNegativeButton:
+ altPositiveButton: joystick button 1
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/NavMeshAreas.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/NavMeshAreas.asset
new file mode 100644
index 00000000..3b0b7c3d
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/NavMeshAreas.asset
@@ -0,0 +1,91 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!126 &1
+NavMeshProjectSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ areas:
+ - name: Walkable
+ cost: 1
+ - name: Not Walkable
+ cost: 1
+ - name: Jump
+ cost: 2
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ m_LastAgentTypeID: -887442657
+ m_Settings:
+ - serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.75
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ debug:
+ m_Flags: 0
+ m_SettingNames:
+ - Humanoid
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PackageManagerSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PackageManagerSettings.asset
new file mode 100644
index 00000000..be4a7974
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PackageManagerSettings.asset
@@ -0,0 +1,43 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &1
+MonoBehaviour:
+ m_ObjectHideFlags: 61
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_EnablePreviewPackages: 0
+ m_EnablePackageDependencies: 0
+ m_AdvancedSettingsExpanded: 1
+ m_ScopedRegistriesSettingsExpanded: 1
+ oneTimeWarningShown: 0
+ m_Registries:
+ - m_Id: main
+ m_Name:
+ m_Url: https://packages.unity.com
+ m_Scopes: []
+ m_IsDefault: 1
+ m_Capabilities: 7
+ m_UserSelectedRegistryName:
+ m_UserAddingNewScopedRegistry: 0
+ m_RegistryInfoDraft:
+ m_ErrorMessage:
+ m_Original:
+ m_Id:
+ m_Name:
+ m_Url:
+ m_Scopes: []
+ m_IsDefault: 0
+ m_Capabilities: 0
+ m_Modified: 0
+ m_Name:
+ m_Url:
+ m_Scopes:
+ -
+ m_SelectedScopeIndex: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/Physics2DSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/Physics2DSettings.asset
new file mode 100644
index 00000000..47880b1c
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/Physics2DSettings.asset
@@ -0,0 +1,56 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!19 &1
+Physics2DSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 4
+ m_Gravity: {x: 0, y: -9.81}
+ m_DefaultMaterial: {fileID: 0}
+ m_VelocityIterations: 8
+ m_PositionIterations: 3
+ m_VelocityThreshold: 1
+ m_MaxLinearCorrection: 0.2
+ m_MaxAngularCorrection: 8
+ m_MaxTranslationSpeed: 100
+ m_MaxRotationSpeed: 360
+ m_BaumgarteScale: 0.2
+ m_BaumgarteTimeOfImpactScale: 0.75
+ m_TimeToSleep: 0.5
+ m_LinearSleepTolerance: 0.01
+ m_AngularSleepTolerance: 2
+ m_DefaultContactOffset: 0.01
+ m_JobOptions:
+ serializedVersion: 2
+ useMultithreading: 0
+ useConsistencySorting: 0
+ m_InterpolationPosesPerJob: 100
+ m_NewContactsPerJob: 30
+ m_CollideContactsPerJob: 100
+ m_ClearFlagsPerJob: 200
+ m_ClearBodyForcesPerJob: 200
+ m_SyncDiscreteFixturesPerJob: 50
+ m_SyncContinuousFixturesPerJob: 50
+ m_FindNearestContactsPerJob: 100
+ m_UpdateTriggerContactsPerJob: 100
+ m_IslandSolverCostThreshold: 100
+ m_IslandSolverBodyCostScale: 1
+ m_IslandSolverContactCostScale: 10
+ m_IslandSolverJointCostScale: 10
+ m_IslandSolverBodiesPerJob: 50
+ m_IslandSolverContactsPerJob: 50
+ m_AutoSimulation: 1
+ m_QueriesHitTriggers: 1
+ m_QueriesStartInColliders: 1
+ m_CallbacksOnDisable: 1
+ m_ReuseCollisionCallbacks: 1
+ m_AutoSyncTransforms: 0
+ m_AlwaysShowColliders: 0
+ m_ShowColliderSleep: 1
+ m_ShowColliderContacts: 0
+ m_ShowColliderAABB: 0
+ m_ContactArrowScale: 0.2
+ m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
+ m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
+ m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
+ m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
+ m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PresetManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PresetManager.asset
new file mode 100644
index 00000000..67a94dae
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/PresetManager.asset
@@ -0,0 +1,7 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1386491679 &1
+PresetManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_DefaultPresets: {}
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectSettings.asset
new file mode 100644
index 00000000..09baa195
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectSettings.asset
@@ -0,0 +1,769 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!129 &1
+PlayerSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 22
+ productGUID: a5edb3c582c1346f396bc5aa201d1182
+ AndroidProfiler: 0
+ AndroidFilterTouchesWhenObscured: 0
+ AndroidEnableSustainedPerformanceMode: 0
+ defaultScreenOrientation: 4
+ targetDevice: 2
+ useOnDemandResources: 0
+ accelerometerFrequency: 60
+ companyName: DefaultCompany
+ productName: sfsafariviewcontroller-chromecustomtabs-sample
+ defaultCursor: {fileID: 0}
+ cursorHotspot: {x: 0, y: 0}
+ m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
+ m_ShowUnitySplashScreen: 1
+ m_ShowUnitySplashLogo: 1
+ m_SplashScreenOverlayOpacity: 1
+ m_SplashScreenAnimation: 1
+ m_SplashScreenLogoStyle: 1
+ m_SplashScreenDrawMode: 0
+ m_SplashScreenBackgroundAnimationZoom: 1
+ m_SplashScreenLogoAnimationZoom: 1
+ m_SplashScreenBackgroundLandscapeAspect: 1
+ m_SplashScreenBackgroundPortraitAspect: 1
+ m_SplashScreenBackgroundLandscapeUvs:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ m_SplashScreenBackgroundPortraitUvs:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ m_SplashScreenLogos: []
+ m_VirtualRealitySplashScreen: {fileID: 0}
+ m_HolographicTrackingLossScreen: {fileID: 0}
+ defaultScreenWidth: 1024
+ defaultScreenHeight: 768
+ defaultScreenWidthWeb: 960
+ defaultScreenHeightWeb: 600
+ m_StereoRenderingPath: 0
+ m_ActiveColorSpace: 0
+ m_MTRendering: 1
+ mipStripping: 0
+ numberOfMipsStripped: 0
+ m_StackTraceTypes: 010000000100000001000000010000000100000001000000
+ iosShowActivityIndicatorOnLoading: -1
+ androidShowActivityIndicatorOnLoading: -1
+ iosUseCustomAppBackgroundBehavior: 0
+ iosAllowHTTPDownload: 1
+ allowedAutorotateToPortrait: 1
+ allowedAutorotateToPortraitUpsideDown: 1
+ allowedAutorotateToLandscapeRight: 1
+ allowedAutorotateToLandscapeLeft: 1
+ useOSAutorotation: 1
+ use32BitDisplayBuffer: 1
+ preserveFramebufferAlpha: 0
+ disableDepthAndStencilBuffers: 0
+ androidStartInFullscreen: 1
+ androidRenderOutsideSafeArea: 1
+ androidUseSwappy: 1
+ androidBlitType: 0
+ defaultIsNativeResolution: 1
+ macRetinaSupport: 1
+ runInBackground: 1
+ captureSingleScreen: 0
+ muteOtherAudioSources: 0
+ Prepare IOS For Recording: 0
+ Force IOS Speakers When Recording: 0
+ deferSystemGesturesMode: 0
+ hideHomeButton: 0
+ submitAnalytics: 1
+ usePlayerLog: 1
+ bakeCollisionMeshes: 0
+ forceSingleInstance: 0
+ useFlipModelSwapchain: 1
+ resizableWindow: 0
+ useMacAppStoreValidation: 0
+ macAppStoreCategory: public.app-category.games
+ gpuSkinning: 1
+ xboxPIXTextureCapture: 0
+ xboxEnableAvatar: 0
+ xboxEnableKinect: 0
+ xboxEnableKinectAutoTracking: 0
+ xboxEnableFitness: 0
+ visibleInBackground: 1
+ allowFullscreenSwitch: 1
+ fullscreenMode: 1
+ xboxSpeechDB: 0
+ xboxEnableHeadOrientation: 0
+ xboxEnableGuest: 0
+ xboxEnablePIXSampling: 0
+ metalFramebufferOnly: 0
+ xboxOneResolution: 0
+ xboxOneSResolution: 0
+ xboxOneXResolution: 3
+ xboxOneMonoLoggingLevel: 0
+ xboxOneLoggingLevel: 1
+ xboxOneDisableEsram: 0
+ xboxOneEnableTypeOptimization: 0
+ xboxOnePresentImmediateThreshold: 0
+ switchQueueCommandMemory: 0
+ switchQueueControlMemory: 16384
+ switchQueueComputeMemory: 262144
+ switchNVNShaderPoolsGranularity: 33554432
+ switchNVNDefaultPoolsGranularity: 16777216
+ switchNVNOtherPoolsGranularity: 16777216
+ switchNVNMaxPublicTextureIDCount: 0
+ switchNVNMaxPublicSamplerIDCount: 0
+ stadiaPresentMode: 0
+ stadiaTargetFramerate: 0
+ vulkanNumSwapchainBuffers: 3
+ vulkanEnableSetSRGBWrite: 0
+ vulkanEnablePreTransform: 0
+ vulkanEnableLateAcquireNextImage: 0
+ m_SupportedAspectRatios:
+ 4:3: 1
+ 5:4: 1
+ 16:10: 1
+ 16:9: 1
+ Others: 1
+ bundleVersion: 0.1
+ preloadedAssets: []
+ metroInputSource: 0
+ wsaTransparentSwapchain: 0
+ m_HolographicPauseOnTrackingLoss: 1
+ xboxOneDisableKinectGpuReservation: 1
+ xboxOneEnable7thCore: 1
+ vrSettings:
+ enable360StereoCapture: 0
+ isWsaHolographicRemotingEnabled: 0
+ enableFrameTimingStats: 0
+ useHDRDisplay: 0
+ D3DHDRBitDepth: 0
+ m_ColorGamuts: 00000000
+ targetPixelDensity: 30
+ resolutionScalingMode: 0
+ androidSupportedAspectRatio: 1
+ androidMaxAspectRatio: 2.1
+ applicationIdentifier: {}
+ buildNumber:
+ Standalone: 0
+ iPhone: 0
+ tvOS: 0
+ overrideDefaultApplicationIdentifier: 0
+ AndroidBundleVersionCode: 1
+ AndroidMinSdkVersion: 19
+ AndroidTargetSdkVersion: 0
+ AndroidPreferredInstallLocation: 1
+ aotOptions:
+ stripEngineCode: 1
+ iPhoneStrippingLevel: 0
+ iPhoneScriptCallOptimization: 0
+ ForceInternetPermission: 0
+ ForceSDCardPermission: 0
+ CreateWallpaper: 0
+ APKExpansionFiles: 0
+ keepLoadedShadersAlive: 0
+ StripUnusedMeshComponents: 1
+ VertexChannelCompressionMask: 4054
+ iPhoneSdkVersion: 988
+ iOSTargetOSVersionString: 11.0
+ tvOSSdkVersion: 0
+ tvOSRequireExtendedGameController: 0
+ tvOSTargetOSVersionString: 11.0
+ uIPrerenderedIcon: 0
+ uIRequiresPersistentWiFi: 0
+ uIRequiresFullScreen: 1
+ uIStatusBarHidden: 1
+ uIExitOnSuspend: 0
+ uIStatusBarStyle: 0
+ appleTVSplashScreen: {fileID: 0}
+ appleTVSplashScreen2x: {fileID: 0}
+ tvOSSmallIconLayers: []
+ tvOSSmallIconLayers2x: []
+ tvOSLargeIconLayers: []
+ tvOSLargeIconLayers2x: []
+ tvOSTopShelfImageLayers: []
+ tvOSTopShelfImageLayers2x: []
+ tvOSTopShelfImageWideLayers: []
+ tvOSTopShelfImageWideLayers2x: []
+ iOSLaunchScreenType: 0
+ iOSLaunchScreenPortrait: {fileID: 0}
+ iOSLaunchScreenLandscape: {fileID: 0}
+ iOSLaunchScreenBackgroundColor:
+ serializedVersion: 2
+ rgba: 0
+ iOSLaunchScreenFillPct: 100
+ iOSLaunchScreenSize: 100
+ iOSLaunchScreenCustomXibPath:
+ iOSLaunchScreeniPadType: 0
+ iOSLaunchScreeniPadImage: {fileID: 0}
+ iOSLaunchScreeniPadBackgroundColor:
+ serializedVersion: 2
+ rgba: 0
+ iOSLaunchScreeniPadFillPct: 100
+ iOSLaunchScreeniPadSize: 100
+ iOSLaunchScreeniPadCustomXibPath:
+ iOSLaunchScreenCustomStoryboardPath:
+ iOSLaunchScreeniPadCustomStoryboardPath:
+ iOSDeviceRequirements: []
+ iOSURLSchemes: []
+ iOSBackgroundModes: 0
+ iOSMetalForceHardShadows: 0
+ metalEditorSupport: 1
+ metalAPIValidation: 1
+ iOSRenderExtraFrameOnPause: 0
+ iosCopyPluginsCodeInsteadOfSymlink: 0
+ appleDeveloperTeamID:
+ iOSManualSigningProvisioningProfileID:
+ tvOSManualSigningProvisioningProfileID:
+ iOSManualSigningProvisioningProfileType: 0
+ tvOSManualSigningProvisioningProfileType: 0
+ appleEnableAutomaticSigning: 0
+ iOSRequireARKit: 0
+ iOSAutomaticallyDetectAndAddCapabilities: 1
+ appleEnableProMotion: 0
+ shaderPrecisionModel: 0
+ clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea
+ templatePackageId: com.unity.template.3d@5.0.4
+ templateDefaultScene: Assets/Scenes/SampleScene.unity
+ useCustomMainManifest: 0
+ useCustomLauncherManifest: 0
+ useCustomMainGradleTemplate: 0
+ useCustomLauncherGradleManifest: 0
+ useCustomBaseGradleTemplate: 0
+ useCustomGradlePropertiesTemplate: 0
+ useCustomProguardFile: 0
+ AndroidTargetArchitectures: 1
+ AndroidSplashScreenScale: 0
+ androidSplashScreen: {fileID: 0}
+ AndroidKeystoreName:
+ AndroidKeyaliasName:
+ AndroidBuildApkPerCpuArchitecture: 0
+ AndroidTVCompatibility: 0
+ AndroidIsGame: 1
+ AndroidEnableTango: 0
+ androidEnableBanner: 1
+ androidUseLowAccuracyLocation: 0
+ androidUseCustomKeystore: 0
+ m_AndroidBanners:
+ - width: 320
+ height: 180
+ banner: {fileID: 0}
+ androidGamepadSupportLevel: 0
+ AndroidMinifyWithR8: 0
+ AndroidMinifyRelease: 0
+ AndroidMinifyDebug: 0
+ AndroidValidateAppBundleSize: 1
+ AndroidAppBundleSizeToValidate: 150
+ m_BuildTargetIcons: []
+ m_BuildTargetPlatformIcons:
+ - m_BuildTarget: Android
+ m_Icons:
+ - m_Textures: []
+ m_Width: 432
+ m_Height: 432
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 324
+ m_Height: 324
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 216
+ m_Height: 216
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 162
+ m_Height: 162
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 108
+ m_Height: 108
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 81
+ m_Height: 81
+ m_Kind: 2
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 192
+ m_Height: 192
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 144
+ m_Height: 144
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 96
+ m_Height: 96
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 72
+ m_Height: 72
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 48
+ m_Height: 48
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 36
+ m_Height: 36
+ m_Kind: 0
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 192
+ m_Height: 192
+ m_Kind: 1
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 144
+ m_Height: 144
+ m_Kind: 1
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 96
+ m_Height: 96
+ m_Kind: 1
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 72
+ m_Height: 72
+ m_Kind: 1
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 48
+ m_Height: 48
+ m_Kind: 1
+ m_SubKind:
+ - m_Textures: []
+ m_Width: 36
+ m_Height: 36
+ m_Kind: 1
+ m_SubKind:
+ m_BuildTargetBatching:
+ - m_BuildTarget: Standalone
+ m_StaticBatching: 1
+ m_DynamicBatching: 0
+ - m_BuildTarget: tvOS
+ m_StaticBatching: 1
+ m_DynamicBatching: 0
+ - m_BuildTarget: Android
+ m_StaticBatching: 1
+ m_DynamicBatching: 0
+ - m_BuildTarget: iPhone
+ m_StaticBatching: 1
+ m_DynamicBatching: 0
+ - m_BuildTarget: WebGL
+ m_StaticBatching: 0
+ m_DynamicBatching: 0
+ m_BuildTargetGraphicsJobs:
+ - m_BuildTarget: MacStandaloneSupport
+ m_GraphicsJobs: 0
+ - m_BuildTarget: Switch
+ m_GraphicsJobs: 1
+ - m_BuildTarget: MetroSupport
+ m_GraphicsJobs: 1
+ - m_BuildTarget: AppleTVSupport
+ m_GraphicsJobs: 0
+ - m_BuildTarget: BJMSupport
+ m_GraphicsJobs: 1
+ - m_BuildTarget: LinuxStandaloneSupport
+ m_GraphicsJobs: 1
+ - m_BuildTarget: PS4Player
+ m_GraphicsJobs: 1
+ - m_BuildTarget: iOSSupport
+ m_GraphicsJobs: 0
+ - m_BuildTarget: WindowsStandaloneSupport
+ m_GraphicsJobs: 1
+ - m_BuildTarget: XboxOnePlayer
+ m_GraphicsJobs: 1
+ - m_BuildTarget: LuminSupport
+ m_GraphicsJobs: 0
+ - m_BuildTarget: AndroidPlayer
+ m_GraphicsJobs: 0
+ - m_BuildTarget: WebGLSupport
+ m_GraphicsJobs: 0
+ m_BuildTargetGraphicsJobMode:
+ - m_BuildTarget: PS4Player
+ m_GraphicsJobMode: 0
+ - m_BuildTarget: XboxOnePlayer
+ m_GraphicsJobMode: 0
+ m_BuildTargetGraphicsAPIs:
+ - m_BuildTarget: AndroidPlayer
+ m_APIs: 150000000b000000
+ m_Automatic: 0
+ - m_BuildTarget: iOSSupport
+ m_APIs: 10000000
+ m_Automatic: 1
+ - m_BuildTarget: AppleTVSupport
+ m_APIs: 10000000
+ m_Automatic: 1
+ - m_BuildTarget: WebGLSupport
+ m_APIs: 0b000000
+ m_Automatic: 1
+ m_BuildTargetVRSettings:
+ - m_BuildTarget: Standalone
+ m_Enabled: 0
+ m_Devices:
+ - Oculus
+ - OpenVR
+ openGLRequireES31: 0
+ openGLRequireES31AEP: 0
+ openGLRequireES32: 0
+ m_TemplateCustomTags: {}
+ mobileMTRendering:
+ Android: 1
+ iPhone: 1
+ tvOS: 1
+ m_BuildTargetGroupLightmapEncodingQuality: []
+ m_BuildTargetGroupLightmapSettings: []
+ m_BuildTargetNormalMapEncoding: []
+ playModeTestRunnerEnabled: 0
+ runPlayModeTestAsEditModeTest: 0
+ actionOnDotNetUnhandledException: 1
+ enableInternalProfiler: 0
+ logObjCUncaughtExceptions: 1
+ enableCrashReportAPI: 0
+ cameraUsageDescription:
+ locationUsageDescription:
+ microphoneUsageDescription:
+ switchNMETAOverride:
+ switchNetLibKey:
+ switchSocketMemoryPoolSize: 6144
+ switchSocketAllocatorPoolSize: 128
+ switchSocketConcurrencyLimit: 14
+ switchScreenResolutionBehavior: 2
+ switchUseCPUProfiler: 0
+ switchUseGOLDLinker: 0
+ switchApplicationID: 0x01004b9000490000
+ switchNSODependencies:
+ switchTitleNames_0:
+ switchTitleNames_1:
+ switchTitleNames_2:
+ switchTitleNames_3:
+ switchTitleNames_4:
+ switchTitleNames_5:
+ switchTitleNames_6:
+ switchTitleNames_7:
+ switchTitleNames_8:
+ switchTitleNames_9:
+ switchTitleNames_10:
+ switchTitleNames_11:
+ switchTitleNames_12:
+ switchTitleNames_13:
+ switchTitleNames_14:
+ switchTitleNames_15:
+ switchPublisherNames_0:
+ switchPublisherNames_1:
+ switchPublisherNames_2:
+ switchPublisherNames_3:
+ switchPublisherNames_4:
+ switchPublisherNames_5:
+ switchPublisherNames_6:
+ switchPublisherNames_7:
+ switchPublisherNames_8:
+ switchPublisherNames_9:
+ switchPublisherNames_10:
+ switchPublisherNames_11:
+ switchPublisherNames_12:
+ switchPublisherNames_13:
+ switchPublisherNames_14:
+ switchPublisherNames_15:
+ switchIcons_0: {fileID: 0}
+ switchIcons_1: {fileID: 0}
+ switchIcons_2: {fileID: 0}
+ switchIcons_3: {fileID: 0}
+ switchIcons_4: {fileID: 0}
+ switchIcons_5: {fileID: 0}
+ switchIcons_6: {fileID: 0}
+ switchIcons_7: {fileID: 0}
+ switchIcons_8: {fileID: 0}
+ switchIcons_9: {fileID: 0}
+ switchIcons_10: {fileID: 0}
+ switchIcons_11: {fileID: 0}
+ switchIcons_12: {fileID: 0}
+ switchIcons_13: {fileID: 0}
+ switchIcons_14: {fileID: 0}
+ switchIcons_15: {fileID: 0}
+ switchSmallIcons_0: {fileID: 0}
+ switchSmallIcons_1: {fileID: 0}
+ switchSmallIcons_2: {fileID: 0}
+ switchSmallIcons_3: {fileID: 0}
+ switchSmallIcons_4: {fileID: 0}
+ switchSmallIcons_5: {fileID: 0}
+ switchSmallIcons_6: {fileID: 0}
+ switchSmallIcons_7: {fileID: 0}
+ switchSmallIcons_8: {fileID: 0}
+ switchSmallIcons_9: {fileID: 0}
+ switchSmallIcons_10: {fileID: 0}
+ switchSmallIcons_11: {fileID: 0}
+ switchSmallIcons_12: {fileID: 0}
+ switchSmallIcons_13: {fileID: 0}
+ switchSmallIcons_14: {fileID: 0}
+ switchSmallIcons_15: {fileID: 0}
+ switchManualHTML:
+ switchAccessibleURLs:
+ switchLegalInformation:
+ switchMainThreadStackSize: 1048576
+ switchPresenceGroupId:
+ switchLogoHandling: 0
+ switchReleaseVersion: 0
+ switchDisplayVersion: 1.0.0
+ switchStartupUserAccount: 0
+ switchTouchScreenUsage: 0
+ switchSupportedLanguagesMask: 0
+ switchLogoType: 0
+ switchApplicationErrorCodeCategory:
+ switchUserAccountSaveDataSize: 0
+ switchUserAccountSaveDataJournalSize: 0
+ switchApplicationAttribute: 0
+ switchCardSpecSize: -1
+ switchCardSpecClock: -1
+ switchRatingsMask: 0
+ switchRatingsInt_0: 0
+ switchRatingsInt_1: 0
+ switchRatingsInt_2: 0
+ switchRatingsInt_3: 0
+ switchRatingsInt_4: 0
+ switchRatingsInt_5: 0
+ switchRatingsInt_6: 0
+ switchRatingsInt_7: 0
+ switchRatingsInt_8: 0
+ switchRatingsInt_9: 0
+ switchRatingsInt_10: 0
+ switchRatingsInt_11: 0
+ switchRatingsInt_12: 0
+ switchLocalCommunicationIds_0:
+ switchLocalCommunicationIds_1:
+ switchLocalCommunicationIds_2:
+ switchLocalCommunicationIds_3:
+ switchLocalCommunicationIds_4:
+ switchLocalCommunicationIds_5:
+ switchLocalCommunicationIds_6:
+ switchLocalCommunicationIds_7:
+ switchParentalControl: 0
+ switchAllowsScreenshot: 1
+ switchAllowsVideoCapturing: 1
+ switchAllowsRuntimeAddOnContentInstall: 0
+ switchDataLossConfirmation: 0
+ switchUserAccountLockEnabled: 0
+ switchSystemResourceMemory: 16777216
+ switchSupportedNpadStyles: 22
+ switchNativeFsCacheSize: 32
+ switchIsHoldTypeHorizontal: 0
+ switchSupportedNpadCount: 8
+ switchSocketConfigEnabled: 0
+ switchTcpInitialSendBufferSize: 32
+ switchTcpInitialReceiveBufferSize: 64
+ switchTcpAutoSendBufferSizeMax: 256
+ switchTcpAutoReceiveBufferSizeMax: 256
+ switchUdpSendBufferSize: 9
+ switchUdpReceiveBufferSize: 42
+ switchSocketBufferEfficiency: 4
+ switchSocketInitializeEnabled: 1
+ switchNetworkInterfaceManagerInitializeEnabled: 1
+ switchPlayerConnectionEnabled: 1
+ switchUseNewStyleFilepaths: 0
+ switchUseMicroSleepForYield: 1
+ switchMicroSleepForYieldTime: 25
+ ps4NPAgeRating: 12
+ ps4NPTitleSecret:
+ ps4NPTrophyPackPath:
+ ps4ParentalLevel: 11
+ ps4ContentID: ED1633-NPXX51362_00-0000000000000000
+ ps4Category: 0
+ ps4MasterVersion: 01.00
+ ps4AppVersion: 01.00
+ ps4AppType: 0
+ ps4ParamSfxPath:
+ ps4VideoOutPixelFormat: 0
+ ps4VideoOutInitialWidth: 1920
+ ps4VideoOutBaseModeInitialWidth: 1920
+ ps4VideoOutReprojectionRate: 60
+ ps4PronunciationXMLPath:
+ ps4PronunciationSIGPath:
+ ps4BackgroundImagePath:
+ ps4StartupImagePath:
+ ps4StartupImagesFolder:
+ ps4IconImagesFolder:
+ ps4SaveDataImagePath:
+ ps4SdkOverride:
+ ps4BGMPath:
+ ps4ShareFilePath:
+ ps4ShareOverlayImagePath:
+ ps4PrivacyGuardImagePath:
+ ps4ExtraSceSysFile:
+ ps4NPtitleDatPath:
+ ps4RemotePlayKeyAssignment: -1
+ ps4RemotePlayKeyMappingDir:
+ ps4PlayTogetherPlayerCount: 0
+ ps4EnterButtonAssignment: 1
+ ps4ApplicationParam1: 0
+ ps4ApplicationParam2: 0
+ ps4ApplicationParam3: 0
+ ps4ApplicationParam4: 0
+ ps4DownloadDataSize: 0
+ ps4GarlicHeapSize: 2048
+ ps4ProGarlicHeapSize: 2560
+ playerPrefsMaxSize: 32768
+ ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ
+ ps4pnSessions: 1
+ ps4pnPresence: 1
+ ps4pnFriends: 1
+ ps4pnGameCustomData: 1
+ playerPrefsSupport: 0
+ enableApplicationExit: 0
+ resetTempFolder: 1
+ restrictedAudioUsageRights: 0
+ ps4UseResolutionFallback: 0
+ ps4ReprojectionSupport: 0
+ ps4UseAudio3dBackend: 0
+ ps4UseLowGarlicFragmentationMode: 1
+ ps4SocialScreenEnabled: 0
+ ps4ScriptOptimizationLevel: 0
+ ps4Audio3dVirtualSpeakerCount: 14
+ ps4attribCpuUsage: 0
+ ps4PatchPkgPath:
+ ps4PatchLatestPkgPath:
+ ps4PatchChangeinfoPath:
+ ps4PatchDayOne: 0
+ ps4attribUserManagement: 0
+ ps4attribMoveSupport: 0
+ ps4attrib3DSupport: 0
+ ps4attribShareSupport: 0
+ ps4attribExclusiveVR: 0
+ ps4disableAutoHideSplash: 0
+ ps4videoRecordingFeaturesUsed: 0
+ ps4contentSearchFeaturesUsed: 0
+ ps4CompatibilityPS5: 0
+ ps4GPU800MHz: 1
+ ps4attribEyeToEyeDistanceSettingVR: 0
+ ps4IncludedModules: []
+ ps4attribVROutputEnabled: 0
+ monoEnv:
+ splashScreenBackgroundSourceLandscape: {fileID: 0}
+ splashScreenBackgroundSourcePortrait: {fileID: 0}
+ blurSplashScreenBackground: 1
+ spritePackerPolicy:
+ webGLMemorySize: 16
+ webGLExceptionSupport: 1
+ webGLNameFilesAsHashes: 0
+ webGLDataCaching: 1
+ webGLDebugSymbols: 0
+ webGLEmscriptenArgs:
+ webGLModulesDirectory:
+ webGLTemplate: APPLICATION:Default
+ webGLAnalyzeBuildSize: 0
+ webGLUseEmbeddedResources: 0
+ webGLCompressionFormat: 1
+ webGLWasmArithmeticExceptions: 0
+ webGLLinkerTarget: 1
+ webGLThreadsSupport: 0
+ webGLDecompressionFallback: 0
+ scriptingDefineSymbols: {}
+ additionalCompilerArguments: {}
+ platformArchitecture: {}
+ scriptingBackend: {}
+ il2cppCompilerConfiguration: {}
+ managedStrippingLevel: {}
+ incrementalIl2cppBuild: {}
+ suppressCommonWarnings: 1
+ allowUnsafeCode: 0
+ useDeterministicCompilation: 1
+ useReferenceAssemblies: 1
+ enableRoslynAnalyzers: 1
+ additionalIl2CppArgs:
+ scriptingRuntimeVersion: 1
+ gcIncremental: 1
+ assemblyVersionValidation: 1
+ gcWBarrierValidation: 0
+ apiCompatibilityLevelPerPlatform: {}
+ m_RenderingPath: 1
+ m_MobileRenderingPath: 1
+ metroPackageName: Template_3D
+ metroPackageVersion:
+ metroCertificatePath:
+ metroCertificatePassword:
+ metroCertificateSubject:
+ metroCertificateIssuer:
+ metroCertificateNotAfter: 0000000000000000
+ metroApplicationDescription: Template_3D
+ wsaImages: {}
+ metroTileShortName:
+ metroTileShowName: 0
+ metroMediumTileShowName: 0
+ metroLargeTileShowName: 0
+ metroWideTileShowName: 0
+ metroSupportStreamingInstall: 0
+ metroLastRequiredScene: 0
+ metroDefaultTileSize: 1
+ metroTileForegroundText: 2
+ metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
+ metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1}
+ metroSplashScreenUseBackgroundColor: 0
+ platformCapabilities: {}
+ metroTargetDeviceFamilies: {}
+ metroFTAName:
+ metroFTAFileTypes: []
+ metroProtocolName:
+ XboxOneProductId:
+ XboxOneUpdateKey:
+ XboxOneSandboxId:
+ XboxOneContentId:
+ XboxOneTitleId:
+ XboxOneSCId:
+ XboxOneGameOsOverridePath:
+ XboxOnePackagingOverridePath:
+ XboxOneAppManifestOverridePath:
+ XboxOneVersion: 1.0.0.0
+ XboxOnePackageEncryption: 0
+ XboxOnePackageUpdateGranularity: 2
+ XboxOneDescription:
+ XboxOneLanguage:
+ - enus
+ XboxOneCapability: []
+ XboxOneGameRating: {}
+ XboxOneIsContentPackage: 0
+ XboxOneEnhancedXboxCompatibilityMode: 0
+ XboxOneEnableGPUVariability: 1
+ XboxOneSockets: {}
+ XboxOneSplashScreen: {fileID: 0}
+ XboxOneAllowedProductIds: []
+ XboxOnePersistentLocalStorageSize: 0
+ XboxOneXTitleMemory: 8
+ XboxOneOverrideIdentityName:
+ XboxOneOverrideIdentityPublisher:
+ vrEditorSettings: {}
+ cloudServicesEnabled:
+ UNet: 1
+ luminIcon:
+ m_Name:
+ m_ModelFolderPath:
+ m_PortalFolderPath:
+ luminCert:
+ m_CertPath:
+ m_SignPackage: 1
+ luminIsChannelApp: 0
+ luminVersion:
+ m_VersionCode: 1
+ m_VersionName:
+ apiCompatibilityLevel: 6
+ activeInputHandler: 0
+ cloudProjectId:
+ framebufferDepthMemorylessMode: 0
+ qualitySettingsNames: []
+ projectName:
+ organizationId:
+ cloudEnabled: 0
+ legacyClampBlendShapeWeights: 0
+ virtualTexturingSupportEnabled: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectVersion.txt b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectVersion.txt
new file mode 100644
index 00000000..4e87e307
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/ProjectVersion.txt
@@ -0,0 +1,2 @@
+m_EditorVersion: 2020.3.8f1
+m_EditorVersionWithRevision: 2020.3.8f1 (507919d4fff5)
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/QualitySettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/QualitySettings.asset
new file mode 100644
index 00000000..7b7658d6
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/QualitySettings.asset
@@ -0,0 +1,232 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!47 &1
+QualitySettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 5
+ m_CurrentQuality: 5
+ m_QualitySettings:
+ - serializedVersion: 2
+ name: Very Low
+ pixelLightCount: 0
+ shadows: 0
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 15
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 1
+ textureQuality: 1
+ anisotropicTextures: 0
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 0
+ lodBias: 0.3
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 4
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Low
+ pixelLightCount: 0
+ shadows: 0
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 20
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 0
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 0
+ lodBias: 0.4
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 16
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Medium
+ pixelLightCount: 1
+ shadows: 1
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 20
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 1
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 1
+ lodBias: 0.7
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 64
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: High
+ pixelLightCount: 2
+ shadows: 2
+ shadowResolution: 1
+ shadowProjection: 1
+ shadowCascades: 2
+ shadowDistance: 40
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 1
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 1
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 256
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Very High
+ pixelLightCount: 3
+ shadows: 2
+ shadowResolution: 2
+ shadowProjection: 1
+ shadowCascades: 2
+ shadowDistance: 70
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 4
+ textureQuality: 0
+ anisotropicTextures: 2
+ antiAliasing: 2
+ softParticles: 1
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 1.5
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 1024
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Ultra
+ pixelLightCount: 4
+ shadows: 2
+ shadowResolution: 2
+ shadowProjection: 1
+ shadowCascades: 4
+ shadowDistance: 150
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 4
+ textureQuality: 0
+ anisotropicTextures: 2
+ antiAliasing: 2
+ softParticles: 1
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 2
+ maximumLODLevel: 0
+ streamingMipmapsActive: 0
+ streamingMipmapsAddAllCameras: 1
+ streamingMipmapsMemoryBudget: 512
+ streamingMipmapsRenderersPerFrame: 512
+ streamingMipmapsMaxLevelReduction: 2
+ streamingMipmapsMaxFileIORequests: 1024
+ particleRaycastBudget: 4096
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 16
+ asyncUploadPersistentBuffer: 1
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ m_PerPlatformDefaultQuality:
+ Android: 2
+ Lumin: 5
+ Nintendo 3DS: 5
+ Nintendo Switch: 5
+ PS4: 5
+ PSP2: 2
+ Stadia: 5
+ Standalone: 5
+ WebGL: 3
+ Windows Store Apps: 5
+ XboxOne: 5
+ iPhone: 2
+ tvOS: 2
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TagManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TagManager.asset
new file mode 100644
index 00000000..1c92a784
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TagManager.asset
@@ -0,0 +1,43 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!78 &1
+TagManager:
+ serializedVersion: 2
+ tags: []
+ layers:
+ - Default
+ - TransparentFX
+ - Ignore Raycast
+ -
+ - Water
+ - UI
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ m_SortingLayers:
+ - name: Default
+ uniqueID: 0
+ locked: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TimeManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TimeManager.asset
new file mode 100644
index 00000000..558a017e
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/TimeManager.asset
@@ -0,0 +1,9 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!5 &1
+TimeManager:
+ m_ObjectHideFlags: 0
+ Fixed Timestep: 0.02
+ Maximum Allowed Timestep: 0.33333334
+ m_TimeScale: 1
+ Maximum Particle Timestep: 0.03
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/UnityConnectSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/UnityConnectSettings.asset
new file mode 100644
index 00000000..6125b308
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/UnityConnectSettings.asset
@@ -0,0 +1,35 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!310 &1
+UnityConnectSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 1
+ m_Enabled: 0
+ m_TestMode: 0
+ m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
+ m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
+ m_ConfigUrl: https://config.uca.cloud.unity3d.com
+ m_DashboardUrl: https://dashboard.unity3d.com
+ m_TestInitMode: 0
+ CrashReportingSettings:
+ m_EventUrl: https://perf-events.cloud.unity3d.com
+ m_Enabled: 0
+ m_LogBufferSize: 10
+ m_CaptureEditorExceptions: 1
+ UnityPurchasingSettings:
+ m_Enabled: 0
+ m_TestMode: 0
+ UnityAnalyticsSettings:
+ m_Enabled: 0
+ m_TestMode: 0
+ m_InitializeOnStartup: 1
+ UnityAdsSettings:
+ m_Enabled: 0
+ m_InitializeOnStartup: 1
+ m_TestMode: 0
+ m_IosGameId:
+ m_AndroidGameId:
+ m_GameIds: {}
+ m_GameId:
+ PerformanceReportingSettings:
+ m_Enabled: 0
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VFXManager.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VFXManager.asset
new file mode 100644
index 00000000..3a95c98b
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VFXManager.asset
@@ -0,0 +1,12 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!937362698 &1
+VFXManager:
+ m_ObjectHideFlags: 0
+ m_IndirectShader: {fileID: 0}
+ m_CopyBufferShader: {fileID: 0}
+ m_SortShader: {fileID: 0}
+ m_StripUpdateShader: {fileID: 0}
+ m_RenderPipeSettingsPath:
+ m_FixedTimeStep: 0.016666668
+ m_MaxDeltaTime: 0.05
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VersionControlSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VersionControlSettings.asset
new file mode 100644
index 00000000..dca28814
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/VersionControlSettings.asset
@@ -0,0 +1,8 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!890905787 &1
+VersionControlSettings:
+ m_ObjectHideFlags: 0
+ m_Mode: Visible Meta Files
+ m_CollabEditorSettings:
+ inProgressEnabled: 1
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/XRSettings.asset b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/XRSettings.asset
new file mode 100644
index 00000000..482590c1
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/ProjectSettings/XRSettings.asset
@@ -0,0 +1,10 @@
+{
+ "m_SettingKeys": [
+ "VR Device Disabled",
+ "VR Device User Alert"
+ ],
+ "m_SettingValues": [
+ "False",
+ "False"
+ ]
+}
\ No newline at end of file
diff --git a/appendix/sample-sfsafariviewcontroller-chromecustomtabs/README.md b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/README.md
new file mode 100644
index 00000000..d6692a09
--- /dev/null
+++ b/appendix/sample-sfsafariviewcontroller-chromecustomtabs/README.md
@@ -0,0 +1,3 @@
+# sample-sfsafariviewcontroller-chromecustomtabs
+
+This sample app shows a simple usage of SFSafariViewController/Chrome Custom Tabs based on https://qiita.com/lucifuges/items/b17d602417a9a249689f . This app does **not** utilize the unity-webview plugin. The unity-webview plugin's various functions depend on UIWebView/WKWebView/WebView and cannot be covered by SFSafariViewController/Chrome Custom Tabs. I however made this app and put it here as SFSafariViewController/Chrome Custom Tabs can be more adequate for some applications and I cannot find any other simple sample code.
diff --git a/build/Rakefile b/build/Rakefile
index d504283d..215cd975 100644
--- a/build/Rakefile
+++ b/build/Rakefile
@@ -20,7 +20,7 @@
require 'fileutils'
-UNITY="/Applications/UnityCurrent/Unity.app/Contents"
+UNITY="/Applications/Unity/Hub/Editor/2022.3.62f2/Unity.app/Contents/MacOS/Unity"
CNT = 2
SRCDIR = Array.new(CNT);
@@ -33,6 +33,7 @@ SRCS[0]=%W|
Editor/UnityWebViewPostprocessBuild.cs
WebViewObject.cs
iOS/WebView.mm
+ iOS/WebViewWithUIWebView.mm
unity-webview-webgl-plugin.jslib
|
SRCDIR[1]="../plugins"
@@ -42,26 +43,32 @@ SRCS[1]=%W|
WebPlayerTemplates
|
-task :default => ["build"]
+task :default => ["build", "pack", "buildnofragment", "packnofragment"]
desc "build"
task :build do
DSTDIR.each do |dir|
sh "git clean -dxf ."
end
+ # for keeping meta files
+ if Dir.exist?('../dist/package')
+ sh "cd ../dist/package; git clean -dxf .; git checkout ."
+ sh "cp -a ../dist/package/* Packager"
+ end
CNT.times do |i|
SRCS[i].each do |src|
dstdir = "#{DSTDIR[i]}/#{File.dirname(src)}"
FileUtils.mkdir_p dstdir
- FileUtils.cp_r "#{SRCDIR[i]}/#{src}", dstdir, {:preserve => true}
+ FileUtils.cp_r "#{SRCDIR[i]}/#{src}", dstdir, preserve:true
end
end
["Android"].each do |t|
Dir.chdir("#{SRCDIR[0]}/#{t}") do
- sh "./install.sh --unity /Applications/Unity5.6.1f1"
+ sh "git clean -dxf .; ./install.sh"
+ sh "git clean -dxf .; ./install.sh --development"
end
end
- ["Mac"].each do |t|
+ ["Mac", "Windows"].each do |t|
Dir.chdir("#{SRCDIR[0]}/#{t}") do
sh "./install.sh"
end
@@ -70,15 +77,58 @@ end
desc "pack"
task :pack do
- sh "#{UNITY}/MacOS/Unity -projectPath `pwd`/Packager -batchmode -quit -executeMethod Packager.Export -logFile LOG"
+ sh "#{UNITY} -projectPath `pwd`/Packager -batchmode -quit -executeMethod AssetDatabase.Refresh || :"
+ sh "#{UNITY} -projectPath `pwd`/Packager -batchmode -quit -executeMethod Packager.Export -logFile LOG"
FileUtils.mv "Packager/unity-webview.unitypackage", "../dist"
sh "(cd Packager; rm -f ../../dist/unity-webview.zip; zip -qr9 ../../dist/unity-webview.zip `find Assets/Plugins -type f` `find Assets/WebGLTemplates -type f` `find Assets/WebPlayerTemplates -type f`)"
+ sh "(cd ../dist/package; rm -rf Assets; unzip -q ../unity-webview.zip; git checkout Assets/Plugins.meta; git checkout Assets/WebGLTemplates.meta; git checkout Assets/WebPlayerTemplates.meta)"
+end
+
+desc "buildnofragment"
+task :buildnofragment do
+ DSTDIR.each do |dir|
+ sh "git clean -dxf ."
+ end
+ # for keeping meta files
+ if Dir.exist?('../dist/package-nofragment')
+ sh "cd ../dist/package-nofragment; git clean -dxf .; git checkout ."
+ sh "cp -a ../dist/package-nofragment/* Packager"
+ end
+ CNT.times do |i|
+ SRCS[i].each do |src|
+ dstdir = "#{DSTDIR[i]}/#{File.dirname(src)}"
+ FileUtils.mkdir_p dstdir
+ FileUtils.cp_r "#{SRCDIR[i]}/#{src}", dstdir, preserve:true
+ end
+ end
+ ["Android"].each do |t|
+ Dir.chdir("#{SRCDIR[0]}/#{t}") do
+ sh "git clean -dxf .; ./install.sh --nofragment"
+ sh "git clean -dxf .; ./install.sh --nofragment --development"
+ end
+ end
+ ["Mac", "Windows"].each do |t|
+ Dir.chdir("#{SRCDIR[0]}/#{t}") do
+ sh "./install.sh"
+ end
+ end
+ sh "sed -i '' -e 's/private static bool nofragment = false/private static bool nofragment = true/' Packager/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs"
+end
+
+desc "packnofragment"
+task :packnofragment do
+ sh "#{UNITY} -projectPath `pwd`/Packager -batchmode -quit -executeMethod AssetDatabase.Refresh || :"
+ sh "#{UNITY} -projectPath `pwd`/Packager -batchmode -quit -executeMethod Packager.Export -logFile LOG"
+ FileUtils.mv "Packager/unity-webview.unitypackage", "../dist/unity-webview-nofragment.unitypackage"
+ sh "(cd Packager; rm -f ../../dist/unity-webview-nofragment.zip; zip -qr9 ../../dist/unity-webview-nofragment.zip `find Assets/Plugins -type f` `find Assets/WebGLTemplates -type f` `find Assets/WebPlayerTemplates -type f`)"
+ sh "(cd ../dist/package-nofragment; rm -rf Assets; unzip -q ../unity-webview-nofragment.zip; git checkout Assets/Plugins.meta; git checkout Assets/WebGLTemplates.meta; git checkout Assets/WebPlayerTemplates.meta)"
end
desc "commit"
task :commit do
- rev = open("|git show HEAD|head -1|awk '{print $2}'").read
- sh "git commit -m \"#{rev}\" ../dist/unity-webview.unitypackage"
+ rev = open("|git show HEAD|head -1|awk '{print $2}'").read.strip
+ sh "git add ../dist"
+ sh "git commit -m 'updated binaries (#{rev}).'"
end
desc "clean"
diff --git a/dist/package-nofragment/Assets.meta b/dist/package-nofragment/Assets.meta
new file mode 100644
index 00000000..cb2d299b
--- /dev/null
+++ b/dist/package-nofragment/Assets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ad965e05b08a049f2bd5d60258452b36
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins.meta b/dist/package-nofragment/Assets/Plugins.meta
new file mode 100644
index 00000000..a772e932
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3fab9ef1899dc4bf18586c6a01172d62
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Android.meta b/dist/package-nofragment/Assets/Plugins/Android.meta
new file mode 100644
index 00000000..a55a2c27
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dbc2200488e7d45189f9a082caf5d637
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl
new file mode 100644
index 00000000..c981c03d
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl differ
diff --git a/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta
new file mode 100644
index 00000000..32a588c1
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4cfd7b6baeab943089b567032c2b3ee6
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl
new file mode 100644
index 00000000..d93266d3
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl differ
diff --git a/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta
new file mode 100644
index 00000000..d4aa3543
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 69e600137811c42778f78793d1022369
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Editor.meta b/dist/package-nofragment/Assets/Plugins/Editor.meta
new file mode 100644
index 00000000..4afc4293
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5415c2bc4488c42739b36767bc7f8c83
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs b/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs
new file mode 100644
index 00000000..b04fcb12
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs
@@ -0,0 +1,568 @@
+#if UNITY_EDITOR
+using System.Collections.Generic;
+using System.Collections;
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Xml;
+using System;
+using UnityEditor.Android;
+#if UNITY_2020_1_OR_NEWER
+using UnityEditor.Build.Reporting;
+#endif
+#if UNITY_2018_1_OR_NEWER
+using UnityEditor.Build;
+#endif
+using UnityEditor.Callbacks;
+using UnityEditor;
+using UnityEngine;
+
+namespace Gree.UnityWebView
+{
+#if UNITY_2020_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuildWithReport, IPostGenerateGradleAndroidProject
+#elif UNITY_2018_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuild, IPostGenerateGradleAndroidProject
+#else
+ public class UnityWebViewPostprocessBuild
+#endif
+ {
+ private static bool nofragment = true;
+
+ //// for android/unity 2018.1 or newer
+ //// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
+ //// cf. https://github.com/Over17/UnityAndroidManifestCallback
+
+#if UNITY_2018_1_OR_NEWER
+#if UNITY_2020_1_OR_NEWER
+ public void OnPreprocessBuild(BuildReport buildReport) {
+ var buildTarget = buildReport.summary.platform;
+#else
+ public void OnPreprocessBuild(BuildTarget buildTarget, string path) {
+#endif
+ if (buildTarget == BuildTarget.Android) {
+ var dev = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ var rel = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ if (!File.Exists(dev) || !File.Exists(rel)) {
+ dev = "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ rel = "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ }
+ var src = (EditorUserBuildSettings.development) ? dev : rel;
+ //Directory.CreateDirectory("Temp/StagingArea/aar");
+ //File.Copy(src, "Temp/StagingArea/aar/WebViewPlugin.aar", true);
+ Directory.CreateDirectory("Assets/Plugins/Android");
+ File.Copy(src, "Assets/Plugins/Android/WebViewPlugin.aar", true);
+ }
+ }
+
+ public void OnPostGenerateGradleAndroidProject(string basePath) {
+ var changed = false;
+ var androidManifest = new AndroidManifest(GetManifestPath(basePath));
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider(basePath) || changed);
+ {
+ var path = GetBuildGradlePath(basePath);
+ var lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n").Split(new[]{'\n'});
+ {
+ var lines = new List();
+ var independencies = false;
+ foreach (var line in lines0) {
+ if (line == "dependencies {") {
+ independencies = true;
+ } else if (independencies && line == "}") {
+ independencies = false;
+ lines.Add(" implementation 'androidx.core:core:1.6.0'");
+ } else if (independencies) {
+ if (line.Contains("implementation(name: 'core")
+ || line.Contains("implementation(name: 'androidx.core.core")
+ || line.Contains("implementation 'androidx.core:core")) {
+ break;
+ }
+ }
+ lines.Add(line);
+ }
+ if (lines.Count > lines0.Length) {
+ File.WriteAllText(path, string.Join("\n", lines) + "\n");
+ }
+ }
+ }
+ {
+ var path = GetGradlePropertiesPath(basePath);
+ var lines0 = "";
+ var lines = "";
+ if (File.Exists(path)) {
+ lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n") + "\n";
+ lines = lines0;
+ }
+ if (!lines.Contains("android.useAndroidX=true")) {
+ lines += "android.useAndroidX=true\n";
+ }
+ if (!lines.Contains("android.enableJetifier=true")) {
+ lines += "android.enableJetifier=true\n";
+ }
+ if (lines != lines0) {
+ File.WriteAllText(path, lines);
+ }
+ }
+ }
+ changed = (androidManifest.SetExported(true) || changed);
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ changed = (androidManifest.AddCamera() || changed);
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ changed = (androidManifest.AddMicrophone() || changed);
+#endif
+ }
+ if (changed) {
+ androidManifest.Save();
+ Debug.Log("unitywebview: adjusted AndroidManifest.xml.");
+ }
+ }
+#endif
+
+ public int callbackOrder {
+ get {
+ return 1;
+ }
+ }
+
+ private string GetManifestPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
+ return pathBuilder.ToString();
+ }
+
+ private string GetBuildGradlePath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("build.gradle");
+ return pathBuilder.ToString();
+ }
+
+ private string GetGradlePropertiesPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ if (basePath.EndsWith("unityLibrary")) {
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("..");
+ }
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("gradle.properties");
+ return pathBuilder.ToString();
+ }
+
+ //// for others
+
+ [PostProcessBuild(100)]
+ public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
+#if UNITY_2018_1_OR_NEWER
+ try {
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar");
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar.meta");
+ Directory.Delete("Assets/Plugins/Android");
+ File.Delete("Assets/Plugins/Android.meta");
+ Directory.Delete("Assets/Plugins");
+ File.Delete("Assets/Plugins.meta");
+ } catch (Exception) {
+ }
+#else
+ if (buildTarget == BuildTarget.Android) {
+ string manifest = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
+ if (!File.Exists(manifest)) {
+ string manifest0 = Path.Combine(Application.dataPath, "../Temp/StagingArea/AndroidManifest-main.xml");
+ if (!File.Exists(manifest0)) {
+ Debug.LogError("unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again.");
+ return;
+ } else {
+ File.Copy(manifest0, manifest, true);
+ }
+ }
+ var changed = false;
+ if (EditorUserBuildSettings.development) {
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
+ } else {
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
+ }
+ var androidManifest = new AndroidManifest(manifest);
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider("Assets/Plugins/Android") || changed);
+ var files = Directory.GetFiles("Assets/Plugins/Android/");
+ var found = false;
+ foreach (var file in files) {
+ if (Regex.IsMatch(file, @"^Assets/Plugins/Android/(androidx\.core\.)?core-.*.aar$")) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ foreach (var file in files) {
+ var match = Regex.Match(file, @"^Assets/Plugins/Android/(core.*.aar).tmpl$");
+ if (match.Success) {
+ var name = match.Groups[1].Value;
+ File.Copy(file, "Assets/Plugins/Android/" + name, true);
+ break;
+ }
+ }
+ }
+ }
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ changed = (androidManifest.AddCamera() || changed);
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ changed = (androidManifest.AddMicrophone() || changed);
+#endif
+ }
+#if UNITY_5_6_0 || UNITY_5_6_1
+ changed = (androidManifest.SetActivityName("net.gree.unitywebview.CUnityPlayerActivity") || changed);
+#endif
+ if (changed) {
+ androidManifest.Save();
+ Debug.LogError("unitywebview: adjusted AndroidManifest.xml and/or WebView.aar. Please rebuild the app.");
+ }
+ }
+#endif
+ if (buildTarget == BuildTarget.iOS) {
+ string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
+ var type = Type.GetType("UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode");
+ if (type == null)
+ {
+ Debug.LogError("unitywebview: failed to get PBXProject. please install iOS build support.");
+ return;
+ }
+ var src = File.ReadAllText(projPath);
+ //dynamic proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ var proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ //proj.ReadFromString(src);
+ {
+ var method = type.GetMethod("ReadFromString");
+ method.Invoke(proj, new object[]{src});
+ }
+ var target = "";
+#if UNITY_2019_3_OR_NEWER
+ //target = proj.GetUnityFrameworkTargetGuid();
+ {
+ var method = type.GetMethod("GetUnityFrameworkTargetGuid");
+ target = (string)method.Invoke(proj, null);
+ }
+#else
+ //target = proj.TargetGuidByName("Unity-iPhone");
+ {
+ var method = type.GetMethod("TargetGuidByName");
+ target = (string)method.Invoke(proj, new object[]{"Unity-iPhone"});
+ }
+#endif
+ //proj.AddFrameworkToProject(target, "WebKit.framework", false);
+ {
+ var method = type.GetMethod("AddFrameworkToProject");
+ method.Invoke(proj, new object[]{target, "WebKit.framework", false});
+ }
+ var cflags = "";
+ if (EditorUserBuildSettings.development) {
+ cflags += " -DUNITYWEBVIEW_DEVELOPMENT";
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS";
+#endif
+ cflags = cflags.Trim();
+ if (!string.IsNullOrEmpty(cflags)) {
+ // proj.AddBuildProperty(target, "OTHER_LDFLAGS", cflags);
+ var method = type.GetMethod("AddBuildProperty", new Type[]{typeof(string), typeof(string), typeof(string)});
+ method.Invoke(proj, new object[]{target, "OTHER_CFLAGS", cflags});
+ }
+ var dst = "";
+ //dst = proj.WriteToString();
+ {
+ var method = type.GetMethod("WriteToString");
+ dst = (string)method.Invoke(proj, null);
+ }
+ File.WriteAllText(projPath, dst);
+ }
+ }
+ }
+
+ internal class AndroidXmlDocument : XmlDocument {
+ private string m_Path;
+ protected XmlNamespaceManager nsMgr;
+ public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
+
+ public AndroidXmlDocument(string path) {
+ m_Path = path;
+ using (var reader = new XmlTextReader(m_Path)) {
+ reader.Read();
+ Load(reader);
+ }
+ nsMgr = new XmlNamespaceManager(NameTable);
+ nsMgr.AddNamespace("android", AndroidXmlNamespace);
+ }
+
+ public string Save() {
+ return SaveAs(m_Path);
+ }
+
+ public string SaveAs(string path) {
+ using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) {
+ writer.Formatting = Formatting.Indented;
+ Save(writer);
+ }
+ return path;
+ }
+ }
+
+ internal class AndroidManifest : AndroidXmlDocument {
+ private readonly XmlElement ManifestElement;
+ private readonly XmlElement ApplicationElement;
+
+ public AndroidManifest(string path) : base(path) {
+ ManifestElement = SelectSingleNode("/manifest") as XmlElement;
+ ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
+ }
+
+ private XmlAttribute CreateAndroidAttribute(string key, string value) {
+ XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
+ attr.Value = value;
+ return attr;
+ }
+
+ internal XmlNode GetActivityWithLaunchIntent() {
+ return
+ SelectSingleNode(
+ "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and "
+ + "intent-filter/category/@android:name='android.intent.category.LAUNCHER']",
+ nsMgr);
+ }
+
+ internal bool SetUsesCleartextTraffic(bool enabled) {
+ // android:usesCleartextTraffic
+ bool changed = false;
+ if (ApplicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ ApplicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ // for api level 33
+ internal bool SetExported(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("exported", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("exported", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetWindowSoftInputMode(string mode) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("windowSoftInputMode", AndroidXmlNamespace) != mode) {
+ activity.SetAttribute("windowSoftInputMode", AndroidXmlNamespace, mode);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetHardwareAccelerated(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetActivityName(string name) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("name", AndroidXmlNamespace) != name) {
+ activity.SetAttribute("name", AndroidXmlNamespace, name);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddFileProvider(string basePath) {
+ bool changed = false;
+ var authorities = PlayerSettings.applicationIdentifier + ".unitywebview.fileprovider";
+ if (SelectNodes("/manifest/application/provider[@android:authorities='" + authorities + "']", nsMgr).Count == 0) {
+ var elem = CreateElement("provider");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "androidx.core.content.FileProvider"));
+ elem.Attributes.Append(CreateAndroidAttribute("authorities", authorities));
+ elem.Attributes.Append(CreateAndroidAttribute("exported", "false"));
+ elem.Attributes.Append(CreateAndroidAttribute("grantUriPermissions", "true"));
+ var meta = CreateElement("meta-data");
+ meta.Attributes.Append(CreateAndroidAttribute("name", "android.support.FILE_PROVIDER_PATHS"));
+ meta.Attributes.Append(CreateAndroidAttribute("resource", "@xml/unitywebview_file_provider_paths"));
+ elem.AppendChild(meta);
+ ApplicationElement.AppendChild(elem);
+ changed = true;
+ var xml = GetFileProviderSettingPath(basePath);
+ if (!File.Exists(xml)) {
+ Directory.CreateDirectory(Path.GetDirectoryName(xml));
+ File.WriteAllText(
+ xml,
+ "\n" +
+ " \n" +
+ "\n");
+ }
+ }
+ return changed;
+ }
+
+ private string GetFileProviderSettingPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("res");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("xml");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("unitywebview_file_provider_paths.xml");
+ return pathBuilder.ToString();
+ }
+
+ internal bool AddCamera() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/data-storage/shared/media#media-location-permission
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.ACCESS_MEDIA_LOCATION']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.ACCESS_MEDIA_LOCATION"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.IMAGE_CAPTURE']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.IMAGE_CAPTURE"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddGallery() {
+ bool changed = false;
+ // for api level 33
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_IMAGES']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_IMAGES"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_VIDEO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_VIDEO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.GET_CONTENT']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.GET_CONTENT"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddMicrophone() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://github.com/gree/unity-webview/issues/679
+ // cf. https://github.com/fluttercommunity/flutter_webview_plugin/issues/138#issuecomment-559307558
+ // cf. https://stackoverflow.com/questions/38917751/webview-webrtc-not-working/68024032#68024032
+ // cf. https://stackoverflow.com/questions/40236925/allowing-microphone-accesspermission-in-webview-android-studio-java/47410311#47410311
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MODIFY_AUDIO_SETTINGS']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MODIFY_AUDIO_SETTINGS"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.RECORD_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.RECORD_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ return changed;
+ }
+ }
+}
+#endif
diff --git a/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta b/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta
new file mode 100644
index 00000000..2d862b52
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b2f5f306eb6e4afcbc074e6efccc188
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/plugins/Mac/WebViewSeparated.bundle.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle.meta
similarity index 87%
rename from plugins/Mac/WebViewSeparated.bundle.meta
rename to dist/package-nofragment/Assets/Plugins/WebView.bundle.meta
index c96417d4..42a688b6 100644
--- a/plugins/Mac/WebViewSeparated.bundle.meta
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 5ad99e2446f6f450185b777a22f98f2a
+guid: 60e7bf38137eb4950b2f02b7d57c1ad3
folderAsset: yes
PluginImporter:
serializedVersion: 1
@@ -29,16 +29,17 @@ PluginImporter:
settings:
CPU: x86_64
OSXIntel:
- enabled: 0
+ enabled: 1
settings:
CPU: AnyCPU
OSXIntel64:
enabled: 1
settings:
- CPU: x86_64
+ CPU: AnyCPU
OSXUniversal:
- enabled: 0
- settings: {}
+ enabled: 1
+ settings:
+ CPU: AnyCPU
Win:
enabled: 0
settings:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents.meta
new file mode 100644
index 00000000..5839b4ea
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ee2bc92b52f924630bfd4aff89395583
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist
new file mode 100644
index 00000000..cf08a6fa
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist
@@ -0,0 +1,48 @@
+
+
+
+
+ BuildMachineOSBuild
+ 25F84
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ WebView
+ CFBundleIdentifier
+ net.gree.unitywebview.WebView
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ WebView
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleVersion
+ 1
+ DTCompiler
+ com.apple.compilers.llvm.clang.1_0
+ DTPlatformBuild
+ 25F70
+ DTPlatformName
+ macosx
+ DTPlatformVersion
+ 26.5
+ DTSDKBuild
+ 25F70
+ DTSDKName
+ macosx26.5
+ DTXcode
+ 2650
+ DTXcodeBuild
+ 17F42
+ LSMinimumSystemVersion
+ 10.13
+
+
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta
new file mode 100644
index 00000000..683244d3
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f5cac4b018fc441519472619c00b4a19
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS.meta
new file mode 100644
index 00000000..402df322
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7583463a0bdf148919eb1819ff8790ab
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView
new file mode 100755
index 00000000..01b503aa
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView differ
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta
new file mode 100644
index 00000000..88fa0aa7
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3472ba57f3d2c40728fe4fa686337555
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources.meta
new file mode 100644
index 00000000..f28fb9be
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b8d99a8979b8445dc8d524538cf76521
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings
new file mode 100644
index 00000000..5e45963c
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings differ
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta
new file mode 100644
index 00000000..da50d44b
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6d60a4326049747b58f27bc930bcd7f4
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta
new file mode 100644
index 00000000..3a521ad0
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b4f429b11407f476894734317eaa0089
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources
new file mode 100644
index 00000000..f4d2e431
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,128 @@
+
+
+
+
+ files
+
+ Resources/InfoPlist.strings
+
+ MiLKDDnrUKr4EmuvhS5VQwxHGK8=
+
+
+ files2
+
+ Resources/InfoPlist.strings
+
+ hash2
+
+ Oc8u4Ht7Mz58F50L9NeYpbcq9qTlhPUeZCcDu/pPyCg=
+
+
+
+ rules
+
+ ^Resources/
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^Resources/
+
+ weight
+ 20
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta
new file mode 100644
index 00000000..82f59825
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7484b160ebd7c40bd9c42038fa7b69a8
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/WebViewObject.cs b/dist/package-nofragment/Assets/Plugins/WebViewObject.cs
new file mode 100644
index 00000000..eddbcd51
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebViewObject.cs
@@ -0,0 +1,2213 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+using UnityEngine;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+#if UNITY_2018_4_OR_NEWER
+using UnityEngine.Networking;
+#endif
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+using UnityEngine.InputSystem;
+#endif
+#endif
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+using System.IO;
+using System.Text.RegularExpressions;
+using UnityEngine.EventSystems;
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_ANDROID
+using UnityEngine.Android;
+#endif
+
+using Callback = System.Action;
+
+namespace Gree.UnityWebView
+{
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ public class UnitySendMessageDispatcher
+ {
+ public static void Dispatch(string name, string method, string message)
+ {
+ GameObject obj = GameObject.Find(name);
+ if (obj != null)
+ obj.SendMessage(method, message);
+ }
+ }
+#endif
+
+ public class WebViewObject : MonoBehaviour
+ {
+ Callback onJS;
+ Callback onError;
+ Callback onHttpError;
+ Callback onStarted;
+ Callback onLoaded;
+ Callback onHooked;
+ Callback onCookies;
+ bool paused;
+ bool visibility;
+ bool alertDialogEnabled;
+ bool scrollBounceEnabled;
+ int mMarginLeft;
+ int mMarginTop;
+ int mMarginRight;
+ int mMarginBottom;
+ bool mMarginRelative;
+ float mMarginLeftComputed;
+ float mMarginTopComputed;
+ float mMarginRightComputed;
+ float mMarginBottomComputed;
+ bool mMarginRelativeComputed;
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+#if UNITY_2018_2_OR_NEWER
+#else
+ byte[] textureDataBuffer;
+#endif
+ string inputString = "";
+ bool hasFocus;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+ byte[] textureDataBuffer;
+ string inputString = "";
+ bool hasFocus;
+#elif UNITY_IPHONE
+ IntPtr webView;
+#elif UNITY_ANDROID
+ AndroidJavaObject webView;
+
+ bool mVisibility;
+ int mKeyboardVisibleHeight;
+ float mResumedTimestamp;
+ int mLastScreenHeight;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ float androidNetworkReachabilityCheckT0 = -1.0f;
+ NetworkReachability? androidNetworkReachability0 = null;
+#endif
+ bool mAllowVideoCapture;
+ bool mAllowAudioCapture;
+
+ void OnApplicationPause(bool paused)
+ {
+ this.paused = paused;
+ if (webView == null)
+ return;
+ // if (!paused && mKeyboardVisibleHeight > 0)
+ // {
+ // webView.Call("SetVisibility", false);
+ // mResumedTimestamp = Time.realtimeSinceStartup;
+ // }
+ webView.Call("OnApplicationPause", paused);
+ }
+
+ void Update()
+ {
+ // NOTE:
+ //
+ // When OnApplicationPause(true) is called and the app is in closing, webView.Call(...)
+ // after that could cause crashes because underlying java instances were closed.
+ //
+ // This has not been cleary confirmed yet. However, as Update() is called once after
+ // OnApplicationPause(true), it is likely correct.
+ //
+ // Base on this assumption, we do nothing here if the app is paused.
+ //
+ // cf. https://github.com/gree/unity-webview/issues/991#issuecomment-1776628648
+ // cf. https://docs.unity3d.com/2020.3/Documentation/Manual/ExecutionOrder.html
+ //
+ // In between frames
+ //
+ // * OnApplicationPause: This is called at the end of the frame where the pause is detected,
+ // effectively between the normal frame updates. One extra frame will be issued after
+ // OnApplicationPause is called to allow the game to show graphics that indicate the
+ // paused state.
+ //
+ if (paused)
+ return;
+ if (webView == null)
+ return;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ var t = Time.time;
+ if (t - 1.0f >= androidNetworkReachabilityCheckT0)
+ {
+ androidNetworkReachabilityCheckT0 = t;
+ var androidNetworkReachability = Application.internetReachability;
+ if (androidNetworkReachability0 != androidNetworkReachability)
+ {
+ androidNetworkReachability0 = androidNetworkReachability;
+ webView.Call("SetNetworkAvailable", androidNetworkReachability != NetworkReachability.NotReachable);
+ }
+ }
+#endif
+ if (mResumedTimestamp != 0.0f && Time.realtimeSinceStartup - mResumedTimestamp > 0.5f)
+ {
+ mResumedTimestamp = 0.0f;
+ webView.Call("SetVisibility", mVisibility);
+ }
+ if (Screen.height != mLastScreenHeight)
+ {
+ mLastScreenHeight = Screen.height;
+ webView.Call("EvaluateJS", "(function() {var e = document.activeElement; if (e != null && e.tagName.toLowerCase() != 'body') {e.blur(); e.focus();}})()");
+ }
+ for (;;) {
+ if (webView == null)
+ break;
+ var s = webView.Call("GetMessage");
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ case "SetKeyboardVisible":
+ SetKeyboardVisible(s.Substring(i + 1));
+ break;
+ case "RequestFileChooserPermissions":
+ RequestFileChooserPermissions();
+ break;
+ }
+ }
+ }
+
+ /// Called from Java native plugin to set when the keyboard is opened
+ public void SetKeyboardVisible(string keyboardVisibleHeight)
+ {
+ if (BottomAdjustmentDisabled())
+ {
+ return;
+ }
+ var keyboardVisibleHeight0 = mKeyboardVisibleHeight;
+ var keyboardVisibleHeight1 = Int32.Parse(keyboardVisibleHeight);
+ if (keyboardVisibleHeight0 != keyboardVisibleHeight1)
+ {
+ mKeyboardVisibleHeight = keyboardVisibleHeight1;
+ SetMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom, mMarginRelative);
+ EvaluateJS("setTimeout(function(){if(document&&document.activeElement){document.activeElement.scrollIntoView();}}, 200);");
+ }
+ }
+
+ /// Called from Java native plugin to request permissions for the file chooser.
+ public void RequestFileChooserPermissions()
+ {
+ var permissions = new List();
+ using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
+ {
+ if (version.GetStatic("SDK_INT") >= 33)
+ {
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_IMAGES");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_VIDEO");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_AUDIO");
+ }
+ }
+ else
+ {
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
+ {
+ permissions.Add(Permission.ExternalStorageRead);
+ }
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
+ {
+ permissions.Add(Permission.ExternalStorageWrite);
+ }
+ }
+ }
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && mAllowVideoCapture)
+ {
+ permissions.Add(Permission.Camera);
+ }
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && mAllowAudioCapture)
+ {
+ permissions.Add(Permission.Microphone);
+ }
+#endif
+ if (permissions.Count > 0)
+ {
+#if UNITY_2020_2_OR_NEWER
+ var grantedCount = 0;
+ var deniedCount = 0;
+ var callbacks = new PermissionCallbacks();
+ callbacks.PermissionGranted += (permission) =>
+ {
+ grantedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDenied += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDeniedAndDontAskAgain += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ Permission.RequestUserPermissions(permissions.ToArray(), callbacks);
+#else
+ StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray()));
+#endif
+ }
+ else
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(true));
+ }
+ }
+
+#if UNITY_2020_2_OR_NEWER
+#else
+ int mRequestPermissionPhase;
+
+ IEnumerator RequestFileChooserPermissionsCoroutine(string[] permissions)
+ {
+ foreach (var permission in permissions)
+ {
+ mRequestPermissionPhase = 0;
+ Permission.RequestUserPermission(permission);
+ // waiting permission dialog that may not be opened.
+ for (var i = 0; i < 8 && mRequestPermissionPhase == 0; i++)
+ {
+ yield return new WaitForSeconds(0.25f);
+ }
+ if (mRequestPermissionPhase == 0)
+ {
+ // permission dialog was not opened.
+ continue;
+ }
+ while (mRequestPermissionPhase == 1)
+ {
+ yield return new WaitForSeconds(0.3f);
+ }
+ }
+ yield return new WaitForSeconds(0.3f);
+ var granted = 0;
+ foreach (var permission in permissions)
+ {
+ if (Permission.HasUserAuthorizedPermission(permission))
+ {
+ granted++;
+ }
+ }
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(granted == permissions.Length));
+ }
+
+ void OnApplicationFocus(bool hasFocus)
+ {
+ if (hasFocus)
+ {
+ if (mRequestPermissionPhase == 1)
+ {
+ mRequestPermissionPhase = 2;
+ }
+ }
+ else
+ {
+ if (mRequestPermissionPhase == 0)
+ {
+ mRequestPermissionPhase = 1;
+ }
+ }
+ }
+#endif
+
+ private IEnumerator CallOnRequestFileChooserPermissionsResult(bool granted)
+ {
+ for (var i = 0; i < 3; i++)
+ {
+ yield return null;
+ }
+ webView.Call("OnRequestFileChooserPermissionsResult", granted);
+ }
+
+ public int AdjustBottomMargin(int bottom)
+ {
+ if (BottomAdjustmentDisabled())
+ {
+ return bottom;
+ }
+ else if (mKeyboardVisibleHeight <= 0)
+ {
+ return bottom;
+ }
+ else
+ {
+ int keyboardHeight = 0;
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
+ {
+ if (view.Call("getGlobalVisibleRect", rect))
+ {
+ int h0 = rect.Get("bottom");
+ view.Call("getWindowVisibleDisplayFrame", rect);
+ int h1 = rect.Get("bottom");
+ keyboardHeight = h0 - h1;
+ }
+ }
+ return (bottom > keyboardHeight) ? bottom : keyboardHeight;
+ }
+ }
+
+ private bool BottomAdjustmentDisabled()
+ {
+#if UNITYWEBVIEW_ANDROID_FORCE_MARGIN_ADJUSTMENT_FOR_KEYBOARD
+ return false;
+#else
+ return
+ !Screen.fullScreen
+ || ((Screen.autorotateToLandscapeLeft || Screen.autorotateToLandscapeRight)
+ && (Screen.autorotateToPortrait || Screen.autorotateToPortraitUpsideDown));
+#endif
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ IntPtr webView;
+#else
+ IntPtr webView;
+#endif
+
+ void Awake()
+ {
+ alertDialogEnabled = true;
+ scrollBounceEnabled = true;
+ mMarginLeftComputed = -9999;
+ mMarginTopComputed = -9999;
+ mMarginRightComputed = -9999;
+ mMarginBottomComputed = -9999;
+ }
+
+ public bool IsKeyboardVisible
+ {
+ get
+ {
+#if !UNITY_EDITOR && UNITY_ANDROID
+ return mKeyboardVisibleHeight > 0;
+#elif !UNITY_EDITOR && UNITY_IPHONE
+ return TouchScreenKeyboard.visible;
+#else
+ return false;
+#endif
+ }
+ }
+
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+ void OnEnable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput += OnTextInput;
+ }
+ }
+
+ void OnDisable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput -= OnTextInput;
+ }
+ }
+
+ void OnTextInput(char ch)
+ {
+ if (hasFocus)
+ {
+ inputString += ch;
+ }
+ }
+#endif
+#endif
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_InitStatic(
+ bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(
+ string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(
+ IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_BitmapARGB(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_InitStatic(bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+#elif UNITY_IPHONE
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, string ua, bool enableWKWebView, int wkContentMode, bool wkAllowsLinkPreview, bool wkAllowsBackForwardNavigationGestures, int radius);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetMargins(
+ IntPtr instance, float left, float top, float right, float bottom, bool relative);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollbarsVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetAlertDialogEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollBounceEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetInteractionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("__Internal")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetBasicAuthInfo(IntPtr instance, string userName, string password);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCache(IntPtr instance, bool includeDiskFiles);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetSuspended(IntPtr instance, bool suspended);
+#elif UNITY_WEBGL
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_init(string name);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setMargins(string name, int left, int top, int right, int bottom);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setVisibility(string name, bool visible);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_loadURL(string name, string url);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_evaluateJS(string name, string js);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_destroy(string name);
+#endif
+
+ public static bool IsWebViewAvailable()
+ {
+#if !UNITY_EDITOR && UNITY_ANDROID
+ using (var plugin = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin"))
+ {
+ return plugin.CallStatic("IsWebViewAvailable");
+ }
+#else
+ return true;
+#endif
+ }
+
+ public bool IsInitialized()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Call("IsInitialized");
+#endif
+ }
+
+ public void Init(
+ Callback cb = null,
+ Callback err = null,
+ Callback httpErr = null,
+ Callback ld = null,
+ Callback started = null,
+ Callback hooked = null,
+ Callback cookies = null,
+ bool transparent = false,
+ bool zoom = true,
+ string ua = "",
+ int radius = 0,
+ // android
+ int androidForceDarkMode = 0, // 0: follow system setting, 1: force dark off, 2: force dark on
+ // ios
+ bool enableWKWebView = true,
+ int wkContentMode = 0, // 0: recommended, 1: mobile, 2: desktop
+ bool wkAllowsLinkPreview = true,
+ bool wkAllowsBackForwardNavigationGestures = true,
+ // editor
+ bool separated = false)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.OSXEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal);
+#endif
+ onJS = cb;
+ onError = err;
+ onHttpError = httpErr;
+ onStarted = started;
+ onLoaded = ld;
+ onHooked = hooked;
+ onCookies = cookies;
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_init(name);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.init", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ Debug.LogError("Webview is not supported on this platform.");
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.WindowsEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11);
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ {
+ var uri = new Uri(_CWebViewPlugin_GetAppPath());
+ var info = File.ReadAllText(uri.LocalPath + "Contents/Info.plist");
+ if (Regex.IsMatch(info, @"CFBundleGetInfoString\s*Unity version [5-9]\.[3-9]")
+ && !Regex.IsMatch(info, @"NSAppTransportSecurity\s*\s*NSAllowsArbitraryLoads\s*\s*")) {
+ Debug.LogWarning("WebViewObject: NSAppTransportSecurity isn't configured to allow HTTP. If you need to allow any HTTP access, please shutdown Unity and invoke:\n/usr/libexec/PlistBuddy -c \"Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true\" /Applications/Unity/Unity.app/Contents/Info.plist");
+ }
+ }
+#if UNITY_EDITOR_OSX
+ // if (string.IsNullOrEmpty(ua)) {
+ // ua = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
+ // }
+#endif
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
+#elif UNITY_IPHONE
+ webView = _CWebViewPlugin_Init(name, transparent, zoom, ua, enableWKWebView, wkContentMode, wkAllowsLinkPreview, wkAllowsBackForwardNavigationGestures, radius);
+#elif UNITY_ANDROID
+ webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin");
+#if UNITY_2021_1_OR_NEWER
+ webView.SetStatic("forceBringToFront", true);
+#endif
+ webView.Call("Init", name, transparent, zoom, androidForceDarkMode, ua, radius);
+#else
+ Debug.LogError("Webview is not supported on this platform.");
+#endif
+ }
+
+ protected virtual void OnDestroy()
+ {
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_destroy(name);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.destroy", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (bg != null)
+ {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ var ptr = webView;
+ webView = IntPtr.Zero;
+ _CWebViewPlugin_Destroy(ptr);
+ Destroy(texture);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (bg != null) {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
+ Destroy(texture);
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Destroy");
+ webView.Dispose();
+ webView = null;
+#endif
+ }
+
+ public void Pause()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ // NOTE: this suspends media playback only.
+ if (webView == null)
+ return;
+ _CWebViewPlugin_SetSuspended(webView, true);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Pause");
+#endif
+ }
+
+ public void Resume()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ // NOTE: this resumes media playback only.
+ _CWebViewPlugin_SetSuspended(webView, false);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Resume");
+#endif
+ }
+
+ // Use this function instead of SetMargins to easily set up a centered window
+ // NOTE: for historical reasons, `center` means the lower left corner and positive y values extend up.
+ public void SetCenterPositionWithScale(Vector2 center, Vector2 scale)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
+#else
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
+#endif
+ }
+
+ public void SetMargins(int left, int top, int right, int bottom, bool relative = false)
+ {
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+#endif
+
+ mMarginLeft = left;
+ mMarginTop = top;
+ mMarginRight = right;
+ mMarginBottom = bottom;
+ mMarginRelative = relative;
+ float ml, mt, mr, mb;
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_IPHONE
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ ml = left / w;
+ mt = top / h;
+ mr = right / w;
+ mb = bottom / h;
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+ }
+#elif UNITY_ANDROID
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ int iw = Display.main.systemWidth;
+ int ih = Display.main.systemHeight;
+ if (!Screen.fullScreen)
+ {
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
+ {
+ view.Call("getDrawingRect", rect);
+ iw = rect.Call("width");
+ ih = rect.Call("height");
+ }
+ }
+ ml = left / w * iw;
+ mt = top / h * ih;
+ mr = right / w * iw;
+ mb = AdjustBottomMargin((int)(bottom / h * ih));
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = AdjustBottomMargin(bottom);
+ }
+#endif
+ bool r = relative;
+
+ if (ml == mMarginLeftComputed
+ && mt == mMarginTopComputed
+ && mr == mMarginRightComputed
+ && mb == mMarginBottomComputed
+ && r == mMarginRelativeComputed)
+ {
+ return;
+ }
+ mMarginLeftComputed = ml;
+ mMarginTopComputed = mt;
+ mMarginRightComputed = mr;
+ mMarginBottomComputed = mb;
+ mMarginRelativeComputed = r;
+
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.setMargins", name, (int)ml, (int)mt, (int)mr, (int)mb);
+#elif UNITY_WEBGL && !UNITY_EDITOR
+ _gree_unity_webview_setMargins(name, (int)ml, (int)mt, (int)mr, (int)mb);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
+#elif UNITY_IPHONE
+ _CWebViewPlugin_SetMargins(webView, ml, mt, mr, mb, r);
+#elif UNITY_ANDROID
+ webView.Call("SetMargins", (int)ml, (int)mt, (int)mr, (int)mb);
+#endif
+ }
+
+ public void SetVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (bg != null)
+ {
+ bg.gameObject.SetActive(v);
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#endif
+ if (GetVisibility() && !v)
+ {
+ EvaluateJS("if (document && document.activeElement) document.activeElement.blur();");
+ }
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_setVisibility(name, v);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.setVisibility", name, v);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ mVisibility = v;
+ webView.Call("SetVisibility", v);
+#endif
+ visibility = v;
+ }
+
+ public bool GetVisibility()
+ {
+ return visibility;
+ }
+
+ public void SetScrollbarsVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollbarsVisibility(webView, v);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetScrollbarsVisibility", v);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetInteractionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetInteractionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetInteractionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetGoogleAppRedirectionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetGoogleAppRedirectionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetGoogleAppRedirectionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetAlertDialogEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetAlertDialogEnabled(webView, e);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetAlertDialogEnabled", e);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ alertDialogEnabled = e;
+ }
+
+ public bool GetAlertDialogEnabled()
+ {
+ return alertDialogEnabled;
+ }
+
+ public void SetScrollBounceEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollBounceEnabled(webView, e);
+#elif UNITY_ANDROID
+ // TODO: UNSUPPORTED
+#else
+ // TODO: UNSUPPORTED
+#endif
+ scrollBounceEnabled = e;
+ }
+
+ public bool GetScrollBounceEnabled()
+ {
+ return scrollBounceEnabled;
+ }
+
+ public void SetCameraAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (webView == null)
+ return;
+ webView.Call("SetCameraAccess", allowed);
+ mAllowVideoCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetMicrophoneAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (webView == null)
+ return;
+ webView.Call("SetMicrophoneAccess", allowed);
+ mAllowAudioCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public bool SetURLPattern(string allowPattern, string denyPattern, string hookPattern)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Call("SetURLPattern", allowPattern, denyPattern, hookPattern);
+#endif
+ }
+
+ public void LoadURL(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ return;
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_loadURL(name, url);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.loadURL", name, url);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("LoadURL", url);
+#endif
+ }
+
+ public void LoadHTML(string html, string baseUrl)
+ {
+ if (string.IsNullOrEmpty(html))
+ return;
+ if (string.IsNullOrEmpty(baseUrl))
+ baseUrl = "";
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("LoadHTML", html, baseUrl);
+#endif
+ }
+
+ public void EvaluateJS(string js)
+ {
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_evaluateJS(name, js);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.evaluateJS", name, js);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("EvaluateJS", js);
+#endif
+ }
+
+ public int Progress()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return 0;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return 0;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return 0;
+ return webView.Get("progress");
+#endif
+ }
+
+ public bool CanGoBack()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Get("canGoBack");
+#endif
+ }
+
+ public bool CanGoForward()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Get("canGoForward");
+#endif
+ }
+
+ public void GoBack()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("GoBack");
+#endif
+ }
+
+ public void GoForward()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("GoForward");
+#endif
+ }
+
+ public void Reload()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Reload");
+#endif
+ }
+
+ public void CallOnError(string error)
+ {
+ if (onError != null)
+ {
+ onError(error);
+ }
+ }
+
+ public void CallOnHttpError(string error)
+ {
+ if (onHttpError != null)
+ {
+ onHttpError(error);
+ }
+ }
+
+ public void CallOnStarted(string url)
+ {
+ if (onStarted != null)
+ {
+ onStarted(url);
+ }
+ }
+
+ public void CallOnLoaded(string url)
+ {
+ if (onLoaded != null)
+ {
+ onLoaded(url);
+ }
+ }
+
+ public void CallFromJS(string message)
+ {
+ if (onJS != null)
+ {
+#if !UNITY_ANDROID
+#if UNITY_2018_4_OR_NEWER
+ message = UnityWebRequest.UnEscapeURL(message);
+#else // UNITY_2018_4_OR_NEWER
+ message = WWW.UnEscapeURL(message);
+#endif // UNITY_2018_4_OR_NEWER
+#endif // !UNITY_ANDROID
+ onJS(message);
+ }
+ }
+
+ public void CallOnHooked(string message)
+ {
+ if (onHooked != null)
+ {
+#if !UNITY_ANDROID
+#if UNITY_2018_4_OR_NEWER
+ message = UnityWebRequest.UnEscapeURL(message);
+#else // UNITY_2018_4_OR_NEWER
+ message = WWW.UnEscapeURL(message);
+#endif // UNITY_2018_4_OR_NEWER
+#endif // !UNITY_ANDROID
+ onHooked(message);
+ }
+ }
+
+ public void CallOnCookies(string cookies)
+ {
+ if (onCookies != null)
+ {
+ onCookies(cookies);
+ }
+ }
+
+ public void AddCustomHeader(string headerKey, string headerValue)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("AddCustomHeader", headerKey, headerValue);
+#endif
+ }
+
+ public string GetCustomHeaderValue(string headerKey)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return null;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return null;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return null;
+ return webView.Call("GetCustomHeaderValue", headerKey);
+#endif
+ }
+
+ public void RemoveCustomHeader(string headerKey)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("RemoveCustomHeader", headerKey);
+#endif
+ }
+
+ public void ClearCustomHeader()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("ClearCustomHeader");
+#endif
+ }
+
+ public void ClearCookie(string url, string name)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookie", url, name);
+#endif
+ }
+
+ public void ClearCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookies");
+#endif
+ }
+
+
+ public void SaveCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SaveCookies");
+#endif
+ }
+
+
+ public void GetCookies(string url)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("GetCookies", url);
+#else
+ //TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetBasicAuthInfo(string userName, string password)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 basic auth not implemented in native plugin
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetBasicAuthInfo(webView, userName, password);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetBasicAuthInfo", userName, password);
+#endif
+ }
+
+ public void ClearCache(bool includeDiskFiles)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 cache clear not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCache(webView, includeDiskFiles);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCache", includeDiskFiles);
+#endif
+ }
+
+
+ public void SetTextZoom(int textZoom)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 text zoom not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ //TODO: UNSUPPORTED
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SetTextZoom", textZoom);
+#endif
+ }
+
+ public void SetMixedContentMode(int mode) // 0: MIXED_CONTENT_ALWAYS_ALLOW, 1: MIXED_CONTENT_NEVER_ALLOW, 2: MIXED_CONTENT_COMPATIBILITY_MODE
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 mixed content mode not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ //TODO: UNSUPPORTED
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SetMixedContentMode", mode);
+#endif
+ }
+
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
+ }
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null) {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus) {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;) {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap) {
+ var w = _CWebViewPlugin_BitmapWidth(webView);
+ var h = _CWebViewPlugin_BitmapHeight(webView);
+ var f = (_CWebViewPlugin_BitmapARGB(webView)) ? TextureFormat.ARGB32 : TextureFormat.RGBA32;
+ if (w > 0 && h > 0) {
+ if (texture == null || texture.width != w || texture.height != h) {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, f, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+#if UNITY_2018_2_OR_NEWER
+#else
+ textureDataBuffer = new byte[w * h * 4];
+#endif
+ }
+ if (texture != null) {
+#if UNITY_2018_2_OR_NEWER
+ var ptr = _CWebViewPlugin_Render(webView, IntPtr.Zero);
+ if (ptr != IntPtr.Zero) {
+ texture.LoadRawTextureData(ptr, w * h * 4);
+ texture.Apply();
+ }
+#else
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+#endif
+ }
+ }
+ }
+ }
+
+ void UpdateBGTransform()
+ {
+ if (bg != null) {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ public int bitmapRefreshCycle = 1;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type) {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
+ break;
+ }
+ switch (Event.current.type) {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
+ break;
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString)) {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null) {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix
+ = Matrix4x4.TRS(
+ new Vector3(0, Screen.height, 0),
+ Quaternion.identity,
+ new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
+ break;
+ }
+ }
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
+ }
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null)
+ {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus)
+ {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;)
+ {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i))
+ {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap)
+ {
+ var w = _CWebViewPlugin_BitmapWidth(webView);
+ var h = _CWebViewPlugin_BitmapHeight(webView);
+ if (w > 0 && h > 0)
+ {
+ if (texture == null || texture.width != w || texture.height != h)
+ {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, TextureFormat.RGBA32, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+ textureDataBuffer = new byte[w * h * 4];
+ }
+ if (textureDataBuffer != null && textureDataBuffer.Length > 0)
+ {
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+ }
+ }
+ }
+ }
+
+ void UpdateBGTransform()
+ {
+ if (bg != null)
+ {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ // On Windows, CapturePreview is heavy; default 10 = refresh every 10th frame for better FPS.
+ public int bitmapRefreshCycle = 10;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type)
+ {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
+ break;
+ }
+ switch (Event.current.type)
+ {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
+ break;
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString))
+ {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null)
+ {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix = Matrix4x4.TRS(new Vector3(0, Screen.height, 0), Quaternion.identity, new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
+ break;
+ }
+ }
+#endif
+ }
+}
diff --git a/dist/package-nofragment/Assets/Plugins/WebViewObject.cs.meta b/dist/package-nofragment/Assets/Plugins/WebViewObject.cs.meta
new file mode 100644
index 00000000..32948634
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/WebViewObject.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d4d2b188f50df4b299eb714ef4360ee9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows.meta b/dist/package-nofragment/Assets/Plugins/Windows.meta
new file mode 100644
index 00000000..4c07dfab
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9a69d5fe42677e84b8100f858cb66df7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x64.meta b/dist/package-nofragment/Assets/Plugins/Windows/x64.meta
new file mode 100644
index 00000000..6d0d8b42
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x64.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0763797fead754049b4255cee0993cb7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt b/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt
new file mode 100644
index 00000000..51c69ca2
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt
@@ -0,0 +1,4 @@
+Place the Windows WebView plugin DLL here:
+ WebView.dll (64-bit, built from plugins/Windows)
+
+Build instructions: see plugins/Windows/README.md
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt.meta b/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt.meta
new file mode 100644
index 00000000..f1b77afa
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x64/README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f62969be983297d4aa100a72a1ef672d
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll b/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll
new file mode 100755
index 00000000..34243f2e
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll differ
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll.meta b/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll.meta
new file mode 100644
index 00000000..a9dd066f
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x64/WebView.dll.meta
@@ -0,0 +1,70 @@
+fileFormatVersion: 2
+guid: 8c2ac249de280af4ca5514719b5a934a
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ :
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 0
+ Exclude Linux64: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 1
+ Exclude Win64: 0
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ DefaultValueInitialized: true
+ OS: Windows
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ - first:
+ Standalone: Win
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x86.meta b/dist/package-nofragment/Assets/Plugins/Windows/x86.meta
new file mode 100644
index 00000000..0dab753e
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x86.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 796e71d62d97fe14fa9d58010d2976fb
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt b/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt
new file mode 100644
index 00000000..ceabd77e
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt
@@ -0,0 +1,4 @@
+Place the Windows WebView plugin DLL here:
+ WebView.dll (32-bit, built from plugins/Windows)
+
+Build instructions: see plugins/Windows/README.md
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt.meta b/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt.meta
new file mode 100644
index 00000000..e8efe961
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x86/README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ea032e1e67ffb2845b15ed2d0ca48d7a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll b/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll
new file mode 100755
index 00000000..908e4d9c
Binary files /dev/null and b/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll differ
diff --git a/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll.meta b/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll.meta
new file mode 100644
index 00000000..7ce685e3
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/Windows/x86/WebView.dll.meta
@@ -0,0 +1,70 @@
+fileFormatVersion: 2
+guid: ed66942567a0baa4e99daba51749d1c5
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ :
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 0
+ Exclude Linux64: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 0
+ Exclude Win64: 1
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: x86
+ DefaultValueInitialized: true
+ OS: Windows
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ - first:
+ Standalone: Win
+ second:
+ enabled: 1
+ settings:
+ CPU: x86
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/iOS.meta b/dist/package-nofragment/Assets/Plugins/iOS.meta
new file mode 100644
index 00000000..352898e8
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/iOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a53a54acdc5d64291aa49766bb494025
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm b/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm
new file mode 100644
index 00000000..01d6dfc4
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm
@@ -0,0 +1,1309 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#if !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
+
+#import
+#import
+
+// NOTE: we need extern without "C" before unity 4.5
+//extern UIViewController *UnityGetGLViewController();
+extern "C" UIViewController *UnityGetGLViewController();
+extern "C" void UnitySendMessage(const char *, const char *, const char *);
+
+// cf. https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak/33365424#33365424
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
+
+@protocol WebViewProtocol
+@property (nonatomic, getter=isOpaque) BOOL opaque;
+@property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
+@property (nonatomic, getter=isHidden) BOOL hidden;
+@property (nonatomic) CGRect frame;
+@property (nullable, nonatomic, weak) id navigationDelegate;
+@property (nullable, nonatomic, weak) id UIDelegate;
+@property (nullable, nonatomic, readonly, copy) NSURL *URL;
+- (void)load:(NSURLRequest *)request;
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
+@property (nonatomic, readonly) BOOL canGoBack;
+@property (nonatomic, readonly) BOOL canGoForward;
+- (void)goBack;
+- (void)goForward;
+- (void)reload;
+- (void)stopLoading;
+- (void)setScrollbarsVisibility:(BOOL)visibility;
+- (void)setScrollBounce:(BOOL)enable;
+@end
+
+@interface WKWebView(WebViewProtocolConformed)
+@end
+
+@implementation WKWebView(WebViewProtocolConformed)
+
+- (void)load:(NSURLRequest *)request
+{
+ WKWebView *webView = (WKWebView *)self;
+ NSURL *url = [request URL];
+ if ([url.absoluteString hasPrefix:@"file:"]) {
+ NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
+ [webView loadFileURL:url allowingReadAccessToURL:top];
+ } else {
+ [webView loadRequest:request];
+ }
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [headerDictionary allKeys]) {
+ [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ WKWebView *webView = (WKWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface CWebViewPlugin : NSObject
+{
+ UIView *webView;
+ NSString *gameObjectName;
+ NSMutableDictionary *customRequestHeader;
+ BOOL googleAppRedirectionEnabled;
+ BOOL alertDialogEnabled;
+ NSRegularExpression *allowRegex;
+ NSRegularExpression *denyRegex;
+ NSRegularExpression *hookRegex;
+ NSString *basicAuthUserName;
+ NSString *basicAuthPassword;
+}
+@end
+
+@implementation CWebViewPlugin
+
+static WKProcessPool *_sharedProcessPool;
+static NSMutableArray *_instances = [[NSMutableArray alloc] init];
+
+- (BOOL)isInitialized
+{
+ return webView != nil;
+}
+
+- (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode allowsLinkPreview:(BOOL)allowsLinkPreview allowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures radius:(int)radius
+{
+ self = [super init];
+
+ gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
+ customRequestHeader = [[NSMutableDictionary alloc] init];
+ googleAppRedirectionEnabled = false;
+ alertDialogEnabled = true;
+ allowRegex = nil;
+ denyRegex = nil;
+ hookRegex = nil;
+ basicAuthUserName = nil;
+ basicAuthPassword = nil;
+ UIView *view = UnityGetGLViewController().view;
+ if (enableWKWebView && [WKWebView class]) {
+ if (_sharedProcessPool == NULL) {
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ }
+ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *controller = [[WKUserContentController alloc] init];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"unityControl"];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"saveDataURL"];
+ {
+ NSString *str = @"\
+window.Unity = { \
+ call: function(msg) { \
+ window.webkit.messageHandlers.unityControl.postMessage(msg); \
+ }, \
+ saveDataURL: function(fileName, dataURL) { \
+ window.webkit.messageHandlers.saveDataURL.postMessage(fileName + '\t' + dataURL); \
+ } \
+}; \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ if (!zoom) {
+ NSString *str = @"\
+(function() { \
+ var meta = document.querySelector('meta[name=viewport]'); \
+ if (meta == null) { \
+ meta = document.createElement('meta'); \
+ meta.name = 'viewport'; \
+ } \
+ meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
+ var head = document.getElementsByTagName('head')[0]; \
+ head.appendChild(meta); \
+})(); \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ configuration.userContentController = controller;
+ configuration.allowsInlineMediaPlayback = true;
+ if (@available(iOS 10.0, *)) {
+ configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+ } else {
+ if (@available(iOS 9.0, *)) {
+ configuration.requiresUserActionForMediaPlayback = NO;
+ } else {
+ configuration.mediaPlaybackRequiresUserAction = NO;
+ }
+ }
+ configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+ configuration.processPool = _sharedProcessPool;
+ if (@available(iOS 13.0, *)) {
+ configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ // cf. https://stackoverflow.com/questions/35554814/wkwebview-xmlhttprequest-with-file-url/44365081#44365081
+ try {
+ [configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+ try {
+ [configuration setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+#endif
+ WKWebView *wkwebView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
+#if UNITYWEBVIEW_DEVELOPMENT
+ NSOperatingSystemVersion version = { 16, 4, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ wkwebView.inspectable = true;
+ }
+#endif
+ wkwebView.allowsLinkPreview = allowsLinkPreview;
+ wkwebView.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
+ webView = wkwebView;
+ webView.UIDelegate = self;
+ webView.navigationDelegate = self;
+ if (radius > 0) {
+ webView.layer.cornerRadius = radius;
+ webView.layer.masksToBounds = YES;
+ }
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
+ }
+ // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
+ // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
+ if (@available(iOS 11.0, *)) {
+ ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+ } else {
+ //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
+ }
+ } else {
+ webView = nil;
+ return self;
+ }
+ if (transparent) {
+ webView.opaque = NO;
+ webView.backgroundColor = [UIColor clearColor];
+ }
+ webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ webView.hidden = YES;
+
+ [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
+
+ [view addSubview:webView];
+
+ //set webview for Unity 6 accessibility hierarchy
+ NSMutableArray *accessibilityElements
+ = view.accessibilityElements ? [view.accessibilityElements mutableCopy] : [NSMutableArray array];
+ [accessibilityElements addObject:(UIAccessibilityElement *)webView];
+ view.accessibilityElements = accessibilityElements;
+
+ return self;
+}
+
+- (void)dispose
+{
+ if (webView != nil) {
+ UIView *webView0 = webView;
+ webView = nil;
+ if ([webView0 isKindOfClass:[WKWebView class]]) {
+ webView0.UIDelegate = nil;
+ webView0.navigationDelegate = nil;
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"saveDataURL"];
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"unityControl"];
+ }
+ [webView0 stopLoading];
+ [webView0 removeFromSuperview];
+ [webView0 removeObserver:self forKeyPath:@"loading"];
+
+ //remove the WebViewObject from Unity hierarchy tree
+ UIView *view = UnityGetGLViewController().view;
+ NSMutableArray *accessibilityElements
+ = view.accessibilityElements ? [view.accessibilityElements mutableCopy] : [NSMutableArray array];
+ [accessibilityElements removeObject: (UIAccessibilityElement *)webView0];
+ view.accessibilityElements = accessibilityElements;
+ }
+ basicAuthPassword = nil;
+ basicAuthUserName = nil;
+ hookRegex = nil;
+ denyRegex = nil;
+ allowRegex = nil;
+ customRequestHeader = nil;
+ gameObjectName = nil;
+}
+
++ (void)resetSharedProcessPool
+{
+ // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
+ if ([obj->webView isKindOfClass:[WKWebView class]]) {
+ WKWebView *webView = (WKWebView *)obj->webView;
+ webView.configuration.processPool = _sharedProcessPool;
+ }
+ }];
+}
+
++ (void)clearCookie:(const char *)name of:(const char *)url
+{
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ if (nsurl == nil) {
+ return;
+ }
+ NSString *nsname = [NSString stringWithUTF8String:name];
+ if (@available(iOS 9.0, *)) {
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ [array
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStore deleteCookie:cookie completionHandler:^{}];
+ }
+ }];
+ }];
+ } else {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }];
+ }
+}
+
++ (void)clearCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+
+ // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
+ NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
+ NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
+ [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
+ [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
+
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [cookieStorage deleteCookie:cookie];
+ }];
+
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ // cf. https://stackoverflow.com/questions/46465070/how-to-delete-cookies-from-wkhttpcookiestore/47928399#47928399
+ NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
+ modifiedSince:date
+ completionHandler:^{}];
+ }
+}
+
++ saveCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+}
+
+- (void)getCookies:(const char *)url
+{
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ [array enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.domain isEqualToString:nsurl.host]) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }];
+ } else {
+ [CWebViewPlugin resetSharedProcessPool];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message {
+
+ // Log out the message received
+ //NSLog(@"Received event %@", message.body);
+ if ([message.name isEqualToString:@"unityControl"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
+ } else if ([message.name isEqualToString:@"saveDataURL"]) {
+ NSRange range = [message.body rangeOfString:@"\t"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *fileName = [[message.body substringWithRange:NSMakeRange(0, range.location)] lastPathComponent];
+ NSString *dataURL = [message.body substringFromIndex:(range.location + 1)];
+ range = [dataURL rangeOfString:@"data:"];
+ if (range.location != 0) {
+ return;
+ }
+ NSString *tmp = [dataURL substringFromIndex:[@"data:" length]];
+ range = [tmp rangeOfString:@";"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *base64data = [tmp substringFromIndex:(range.location + 1 + [@"base64," length])];
+ NSString *type = [tmp substringWithRange:NSMakeRange(0, range.location)];
+ NSData *data = [[NSData alloc] initWithBase64EncodedString:base64data options:0];
+ NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ path = [path stringByAppendingString:@"/Downloads"];
+ BOOL isDir;
+ NSError *err = nil;
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
+ if (!isDir) {
+ return;
+ }
+ } else {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
+ if (err != nil) {
+ return;
+ }
+ }
+ NSString *prefix = [path stringByAppendingString:@"/"];
+ path = [prefix stringByAppendingString:fileName];
+ int count = 0;
+ while ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ count++;
+ NSString *name = [fileName stringByDeletingPathExtension];
+ NSString *ext = [fileName pathExtension];
+ if (ext.length == 0) {
+ path = [NSString stringWithFormat:@"%@%@ (%d)", prefix, name, count];
+ } else {
+ path = [NSString stringWithFormat:@"%@%@ (%d).%@", prefix, name, count, ext];
+ }
+ }
+ [data writeToFile:path atomically:YES];
+ }
+
+ /*
+ // Then pull something from the device using the message body
+ NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
+
+ // Execute some JavaScript using the result?
+ NSString *exec_template = @"set_headline(\"received: %@\");";
+ NSString *exec = [NSString stringWithFormat:exec_template, version];
+ [webView evaluateJavaScript:exec completionHandler:nil];
+ */
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (webView == nil)
+ return;
+
+ if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+
+ }
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", "webViewWebContentProcessDidTerminate");
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ if (!navigationAction.targetFrame.isMainFrame) {
+ [wkWebView loadRequest:navigationAction.request];
+ }
+ return nil;
+}
+
+- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ if (webView == nil) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ NSURL *nsurl = [navigationAction.request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
+ && ![url hasPrefix:@"about:srcdoc"] // for iframe srcdoc attribute
+ && ![url hasPrefix:@"file:"]
+ && ![url hasPrefix:@"http:"]
+ && ![url hasPrefix:@"https:"]) {
+ if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
+ && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else {
+ if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
+ // If the custom header is not attached, give it and make a request again.
+ if (![self isSetupedCustomHeader:[navigationAction request]]) {
+ //NSLog(@"navi ... %@", navigationAction);
+ [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ }
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ // cf. https://stackoverflow.com/questions/37086605/disable-wkwebview-for-opening-links-to-redirect-to-apps-installed-on-my-iphone/76948270#76948270
+ if (!googleAppRedirectionEnabled
+ && [url hasPrefix:@"https://www.google.com/"]
+ && navigationAction.navigationType == WKNavigationTypeLinkActivated) {
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode >= 400) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
+ }
+
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+// alert
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler();
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler();
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// confirm
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(NO);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ completionHandler(YES);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(NO);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// prompt
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(nil);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:prompt
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+ textField.text = defaultText;
+ }];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
+ completionHandler(input);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(nil);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
+{
+ NSURLSessionAuthChallengeDisposition disposition;
+ NSURLCredential *credential;
+ if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
+ disposition = NSURLSessionAuthChallengeUseCredential;
+ credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
+ } else {
+ disposition = NSURLSessionAuthChallengePerformDefaultHandling;
+ credential = nil;
+ }
+ completionHandler(disposition, credential);
+}
+
+- (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
+{
+ // Check for additional custom header.
+ for (NSString *key in [customRequestHeader allKeys]) {
+ if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [customRequestHeader allKeys]) {
+ [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
+{
+ if (webView == nil)
+ return;
+ UIView *view = UnityGetGLViewController().view;
+ CGRect frame = webView.frame;
+ CGRect screen = view.bounds;
+ if (relative) {
+ frame.size.width = floor(screen.size.width * (1.0f - left - right));
+ frame.size.height = floor(screen.size.height * (1.0f - top - bottom));
+ frame.origin.x = floor(screen.size.width * left);
+ frame.origin.y = floor(screen.size.height * top);
+ } else {
+ CGFloat scale = 1.0f / [self getScale:view];
+ frame.size.width = floor(screen.size.width - scale * (left + right));
+ frame.size.height = floor(screen.size.height - scale * (top + bottom));
+ frame.origin.x = floor(scale * left);
+ frame.origin.y = floor(scale * top);
+ }
+ webView.frame = frame;
+}
+
+- (CGFloat)getScale:(UIView *)view
+{
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ return view.window.screen.nativeScale;
+ return view.contentScaleFactor;
+}
+
+- (void)setVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ webView.hidden = visibility ? NO : YES;
+}
+
+- (void)setInteractionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ webView.userInteractionEnabled = enabled;
+}
+
+- (void)setGoogleAppRedirectionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ googleAppRedirectionEnabled = enabled;
+}
+
+- (void)setAlertDialogEnabled:(BOOL)enabled
+{
+ alertDialogEnabled = enabled;
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ [webView setScrollbarsVisibility:visibility];
+}
+
+- (void)setScrollBounceEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ [webView setScrollBounce:enabled];
+}
+
+- (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
+{
+ NSError *err = nil;
+ NSRegularExpression *allow = nil;
+ NSRegularExpression *deny = nil;
+ NSRegularExpression *hook = nil;
+ if (allowPattern == nil || *allowPattern == '\0') {
+ allow = nil;
+ } else {
+ allow
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (denyPattern == nil || *denyPattern == '\0') {
+ deny = nil;
+ } else {
+ deny
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (hookPattern == nil || *hookPattern == '\0') {
+ hook = nil;
+ } else {
+ hook
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ allowRegex = allow;
+ denyRegex = deny;
+ hookRegex = hook;
+ return YES;
+}
+
+- (void)loadURL:(const char *)url
+{
+ if (webView == nil)
+ return;
+ NSString *urlStr = [NSString stringWithUTF8String:url];
+ NSURL *nsurl = [NSURL URLWithString:urlStr];
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
+ [webView load:request];
+}
+
+- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
+{
+ if (webView == nil)
+ return;
+ NSString *htmlStr = [NSString stringWithUTF8String:html];
+ NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
+ NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
+ [webView loadHTML:htmlStr baseURL:baseNSUrl];
+}
+
+- (void)evaluateJS:(const char *)js
+{
+ if (webView == nil)
+ return;
+ NSString *jsStr = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
+}
+
+- (int)progress
+{
+ if (webView == nil)
+ return 0;
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ return (int)([(WKWebView *)webView estimatedProgress] * 100);
+ } else {
+ return 0;
+ }
+}
+
+- (BOOL)canGoBack
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoForward];
+}
+
+- (void)goBack
+{
+ if (webView == nil)
+ return;
+ [webView goBack];
+}
+
+- (void)goForward
+{
+ if (webView == nil)
+ return;
+ [webView goForward];
+}
+
+- (void)reload
+{
+ if (webView == nil)
+ return;
+ [webView reload];
+}
+
+- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *valueString = [NSString stringWithUTF8String:headerValue];
+
+ [customRequestHeader setObject:valueString forKey:keyString];
+}
+
+- (void)removeCustomRequestHeader:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+
+ if ([[customRequestHeader allKeys]containsObject:keyString]) {
+ [customRequestHeader removeObjectForKey:keyString];
+ }
+}
+
+- (void)clearCustomRequestHeader
+{
+ [customRequestHeader removeAllObjects];
+}
+
+- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *result = [customRequestHeader objectForKey:keyString];
+ if (!result) {
+ return NULL;
+ }
+
+ const char *s = [result UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+- (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
+{
+ basicAuthUserName = [NSString stringWithUTF8String:userName];
+ basicAuthPassword = [NSString stringWithUTF8String:password];
+}
+
+- (void)clearCache:(BOOL)includeDiskFiles
+{
+ if (webView == nil)
+ return;
+ NSMutableSet *types = [NSMutableSet setWithArray:@[WKWebsiteDataTypeMemoryCache]];
+ if (includeDiskFiles) {
+ [types addObject:WKWebsiteDataTypeDiskCache];
+ }
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:types modifiedSince:date completionHandler:^{}];
+}
+
+- (void)setAllMediaPlaybackSuspended:(BOOL)suspended
+{
+ NSOperatingSystemVersion version = { 15, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ [(WKWebView *)webView setAllMediaPlaybackSuspended:suspended completionHandler:nil];
+ }
+ }
+}
+@end
+
+extern "C" {
+ BOOL _CWebViewPlugin_IsInitialized(void *instance);
+ void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius);
+ void _CWebViewPlugin_Destroy(void *instance);
+ void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative);
+ void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
+ BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
+ void _CWebViewPlugin_LoadURL(void *instance, const char *url);
+ void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
+ void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
+ int _CWebViewPlugin_Progress(void *instance);
+ BOOL _CWebViewPlugin_CanGoBack(void *instance);
+ BOOL _CWebViewPlugin_CanGoForward(void *instance);
+ void _CWebViewPlugin_GoBack(void *instance);
+ void _CWebViewPlugin_GoForward(void *instance);
+ void _CWebViewPlugin_Reload(void *instance);
+ void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
+ void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCustomHeader(void *instance);
+ void _CWebViewPlugin_ClearCookie(const char *url, const char *name);
+ void _CWebViewPlugin_ClearCookies();
+ void _CWebViewPlugin_SaveCookies();
+ void _CWebViewPlugin_GetCookies(void *instance, const char *url);
+ const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
+ void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
+ void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
+ void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended);
+}
+
+BOOL _CWebViewPlugin_IsInitialized(void *instance)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin isInitialized];
+}
+
+void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius)
+{
+ if (! (enableWKWebView && [WKWebView class]))
+ return nil;
+ WKContentMode wkContentMode = WKContentModeRecommended;
+ switch (contentMode) {
+ case 1:
+ wkContentMode = WKContentModeMobile;
+ break;
+ case 2:
+ wkContentMode = WKContentModeDesktop;
+ break;
+ default:
+ wkContentMode = WKContentModeRecommended;
+ break;
+ }
+ CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode allowsLinkPreview:allowsLinkPreview allowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures radius:radius];
+ [_instances addObject:webViewPlugin];
+ return (__bridge_retained void *)webViewPlugin;
+}
+
+void _CWebViewPlugin_Destroy(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
+ [_instances removeObject:webViewPlugin];
+ [webViewPlugin dispose];
+ webViewPlugin = nil;
+}
+
+void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
+}
+
+void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setInteractionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setGoogleAppRedirectionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAlertDialogEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollbarsVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollBounceEnabled:enabled];
+}
+
+BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
+}
+
+void _CWebViewPlugin_LoadURL(void *instance, const char *url)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadURL:url];
+}
+
+void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadHTML:html baseURL:baseUrl];
+}
+
+void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin evaluateJS:js];
+}
+
+int _CWebViewPlugin_Progress(void *instance)
+{
+ if (instance == NULL)
+ return 0;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin progress];
+}
+
+BOOL _CWebViewPlugin_CanGoBack(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoBack];
+}
+
+BOOL _CWebViewPlugin_CanGoForward(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoForward];
+}
+
+void _CWebViewPlugin_GoBack(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goBack];
+}
+
+void _CWebViewPlugin_GoForward(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goForward];
+}
+
+void _CWebViewPlugin_Reload(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin reload];
+}
+
+void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
+}
+
+void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin removeCustomRequestHeader:headerKey];
+}
+
+void _CWebViewPlugin_ClearCustomHeader(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCustomRequestHeader];
+}
+
+void _CWebViewPlugin_ClearCookie(const char *url, const char *name)
+{
+ [CWebViewPlugin clearCookie:name of:url];
+}
+
+void _CWebViewPlugin_ClearCookies()
+{
+ [CWebViewPlugin clearCookies];
+}
+
+void _CWebViewPlugin_SaveCookies()
+{
+ [CWebViewPlugin saveCookies];
+}
+
+void _CWebViewPlugin_GetCookies(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin getCookies:url];
+}
+
+const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return NULL;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin getCustomRequestHeaderValue:headerKey];
+}
+
+void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setBasicAuthInfo:userName password:password];
+}
+
+void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCache:includeDiskFiles];
+}
+
+void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAllMediaPlaybackSuspended:suspended];
+}
+#endif // !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
diff --git a/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm.meta b/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm.meta
new file mode 100644
index 00000000..fb91d132
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/iOS/WebView.mm.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 2cabb4f60971742a28f3bd04e65de504
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm b/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm
new file mode 100644
index 00000000..7909a36f
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm
@@ -0,0 +1,1385 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+
+#import
+#import
+
+// NOTE: we need extern without "C" before unity 4.5
+//extern UIViewController *UnityGetGLViewController();
+extern "C" UIViewController *UnityGetGLViewController();
+extern "C" void UnitySendMessage(const char *, const char *, const char *);
+
+// cf. https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak/33365424#33365424
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
+
+@protocol WebViewProtocol
+@property (nonatomic, getter=isOpaque) BOOL opaque;
+@property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
+@property (nonatomic, getter=isHidden) BOOL hidden;
+@property (nonatomic, getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
+@property (nonatomic) CGRect frame;
+@property (nonatomic, readonly, strong) UIScrollView *scrollView;
+@property (nullable, nonatomic, assign) id delegate;
+@property (nullable, nonatomic, weak) id navigationDelegate;
+@property (nullable, nonatomic, weak) id UIDelegate;
+@property (nullable, nonatomic, readonly, copy) NSURL *URL;
+- (void)load:(NSURLRequest *)request;
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
+@property (nonatomic, readonly) BOOL canGoBack;
+@property (nonatomic, readonly) BOOL canGoForward;
+- (void)goBack;
+- (void)goForward;
+- (void)reload;
+- (void)stopLoading;
+- (void)setScrollbarsVisibility:(BOOL)visibility;
+- (void)setScrollBounce:(BOOL)enable;
+@end
+
+@interface WKWebView(WebViewProtocolConformed)
+@end
+
+@implementation WKWebView(WebViewProtocolConformed)
+
+@dynamic delegate;
+
+- (void)load:(NSURLRequest *)request
+{
+ WKWebView *webView = (WKWebView *)self;
+ NSURL *url = [request URL];
+ if ([url.absoluteString hasPrefix:@"file:"]) {
+ NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
+ [webView loadFileURL:url allowingReadAccessToURL:top];
+ } else {
+ [webView loadRequest:request];
+ }
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [headerDictionary allKeys]) {
+ [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ WKWebView *webView = (WKWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface UIWebView(WebViewProtocolConformed)
+@end
+
+@implementation UIWebView(WebViewProtocolConformed)
+
+@dynamic navigationDelegate;
+@dynamic UIDelegate;
+
+- (NSURL *)URL
+{
+ return [NSURL URLWithString:[self stringByEvaluatingJavaScriptFromString:@"document.URL"]];
+}
+
+- (void)load:(NSURLRequest *)request
+{
+ UIWebView *webView = (UIWebView *)self;
+ [webView loadRequest:request];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ UIWebView *webView = (UIWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler
+{
+ NSString *result = [self stringByEvaluatingJavaScriptFromString:javaScriptString];
+ if (completionHandler) {
+ completionHandler(result, nil);
+ }
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ UIWebView *webView = (UIWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ UIWebView *webView = (UIWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface CWebViewPlugin : NSObject
+{
+ UIView *webView;
+ NSString *gameObjectName;
+ NSMutableDictionary *customRequestHeader;
+ BOOL googleAppRedirectionEnabled;
+ BOOL alertDialogEnabled;
+ NSRegularExpression *allowRegex;
+ NSRegularExpression *denyRegex;
+ NSRegularExpression *hookRegex;
+ NSString *basicAuthUserName;
+ NSString *basicAuthPassword;
+}
+@end
+
+@implementation CWebViewPlugin
+
+static WKProcessPool *_sharedProcessPool;
+static NSMutableArray *_instances = [[NSMutableArray alloc] init];
+
+- (BOOL)isInitialized
+{
+ return webView != nil;
+}
+
+- (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode allowsLinkPreview:(BOOL)allowsLinkPreview allowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures radius:(int)radius
+{
+ self = [super init];
+
+ gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
+ customRequestHeader = [[NSMutableDictionary alloc] init];
+ googleAppRedirectionEnabled = false;
+ alertDialogEnabled = true;
+ allowRegex = nil;
+ denyRegex = nil;
+ hookRegex = nil;
+ basicAuthUserName = nil;
+ basicAuthPassword = nil;
+ UIView *view = UnityGetGLViewController().view;
+ if (enableWKWebView && [WKWebView class]) {
+ if (_sharedProcessPool == NULL) {
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ }
+ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *controller = [[WKUserContentController alloc] init];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"unityControl"];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"saveDataURL"];
+ {
+ NSString *str = @"\
+window.Unity = { \
+ call: function(msg) { \
+ window.webkit.messageHandlers.unityControl.postMessage(msg); \
+ }, \
+ saveDataURL: function(fileName, dataURL) { \
+ window.webkit.messageHandlers.saveDataURL.postMessage(fileName + '\t' + dataURL); \
+ } \
+}; \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ if (!zoom) {
+ NSString *str = @"\
+(function() { \
+ var meta = document.querySelector('meta[name=viewport]'); \
+ if (meta == null) { \
+ meta = document.createElement('meta'); \
+ meta.name = 'viewport'; \
+ } \
+ meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
+ var head = document.getElementsByTagName('head')[0]; \
+ head.appendChild(meta); \
+})(); \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ configuration.userContentController = controller;
+ configuration.allowsInlineMediaPlayback = true;
+ if (@available(iOS 10.0, *)) {
+ configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+ } else {
+ if (@available(iOS 9.0, *)) {
+ configuration.requiresUserActionForMediaPlayback = NO;
+ } else {
+ configuration.mediaPlaybackRequiresUserAction = NO;
+ }
+ }
+ configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+ configuration.processPool = _sharedProcessPool;
+ if (@available(iOS 13.0, *)) {
+ configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ // cf. https://stackoverflow.com/questions/35554814/wkwebview-xmlhttprequest-with-file-url/44365081#44365081
+ try {
+ [configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+ try {
+ [configuration setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+#endif
+ WKWebView *wkwebView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
+#if UNITYWEBVIEW_DEVELOPMENT
+ NSOperatingSystemVersion version = { 16, 4, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ wkwebView.inspectable = true;
+ }
+#endif
+ wkwebView.allowsLinkPreview = allowsLinkPreview;
+ wkwebView.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
+ webView = wkwebView;
+ webView.UIDelegate = self;
+ webView.navigationDelegate = self;
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
+ }
+ // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
+ // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
+ if (@available(iOS 11.0, *)) {
+ ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+ } else {
+ //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
+ }
+ } else {
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ [[NSUserDefaults standardUserDefaults]
+ registerDefaults:@{ @"UserAgent": [[NSString alloc] initWithUTF8String:ua] }];
+ }
+ UIWebView *uiwebview = [[UIWebView alloc] initWithFrame:view.frame];
+ uiwebview.allowsInlineMediaPlayback = YES;
+ uiwebview.mediaPlaybackRequiresUserAction = NO;
+ webView = uiwebview;
+ webView.delegate = self;
+ }
+ if (transparent) {
+ webView.opaque = NO;
+ webView.backgroundColor = [UIColor clearColor];
+ }
+ if (radius > 0) {
+ webView.layer.cornerRadius = radius;
+ webView.layer.masksToBounds = YES;
+ }
+ webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ webView.hidden = YES;
+
+ [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
+
+ [view addSubview:webView];
+
+ return self;
+}
+
+- (void)dispose
+{
+ if (webView != nil) {
+ UIView *webView0 = webView;
+ webView = nil;
+ if ([webView0 isKindOfClass:[WKWebView class]]) {
+ webView0.UIDelegate = nil;
+ webView0.navigationDelegate = nil;
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"saveDataURL"];
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"unityControl"];
+ } else {
+ webView0.delegate = nil;
+ }
+ [webView0 stopLoading];
+ [webView0 removeFromSuperview];
+ [webView0 removeObserver:self forKeyPath:@"loading"];
+ }
+ basicAuthPassword = nil;
+ basicAuthUserName = nil;
+ hookRegex = nil;
+ denyRegex = nil;
+ allowRegex = nil;
+ customRequestHeader = nil;
+ gameObjectName = nil;
+}
+
++ (void)resetSharedProcessPool
+{
+ // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
+ if ([obj->webView isKindOfClass:[WKWebView class]]) {
+ WKWebView *webView = (WKWebView *)obj->webView;
+ webView.configuration.processPool = _sharedProcessPool;
+ }
+ }];
+}
+
++ (void)clearCookie:(const char *)name of:(const char *)url
+{
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ if (nsurl == nil) {
+ return;
+ }
+ NSString *nsname = [NSString stringWithUTF8String:name];
+ if (@available(iOS 9.0, *)) {
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ [array
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStore deleteCookie:cookie completionHandler:^{}];
+ }
+ }];
+ }];
+ } else {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }];
+ }
+}
+
++ (void)clearCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+
+ // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
+ NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
+ NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
+ [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
+ [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
+
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [cookieStorage deleteCookie:cookie];
+ }];
+
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ // cf. https://stackoverflow.com/questions/46465070/how-to-delete-cookies-from-wkhttpcookiestore/47928399#47928399
+ NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
+ modifiedSince:date
+ completionHandler:^{}];
+ }
+}
+
++ saveCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+}
+
+- (void)getCookies:(const char *)url
+{
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ [array enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.domain isEqualToString:nsurl.host]) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }];
+ } else {
+ [CWebViewPlugin resetSharedProcessPool];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message {
+
+ // Log out the message received
+ //NSLog(@"Received event %@", message.body);
+ if ([message.name isEqualToString:@"unityControl"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
+ } else if ([message.name isEqualToString:@"saveDataURL"]) {
+ NSRange range = [message.body rangeOfString:@"\t"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *fileName = [[message.body substringWithRange:NSMakeRange(0, range.location)] lastPathComponent];
+ NSString *dataURL = [message.body substringFromIndex:(range.location + 1)];
+ range = [dataURL rangeOfString:@"data:"];
+ if (range.location != 0) {
+ return;
+ }
+ NSString *tmp = [dataURL substringFromIndex:[@"data:" length]];
+ range = [tmp rangeOfString:@";"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *base64data = [tmp substringFromIndex:(range.location + 1 + [@"base64," length])];
+ NSString *type = [tmp substringWithRange:NSMakeRange(0, range.location)];
+ NSData *data = [[NSData alloc] initWithBase64EncodedString:base64data options:0];
+ NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ path = [path stringByAppendingString:@"/Downloads"];
+ BOOL isDir;
+ NSError *err = nil;
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
+ if (!isDir) {
+ return;
+ }
+ } else {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
+ if (err != nil) {
+ return;
+ }
+ }
+ NSString *prefix = [path stringByAppendingString:@"/"];
+ path = [prefix stringByAppendingString:fileName];
+ int count = 0;
+ while ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ count++;
+ NSString *name = [fileName stringByDeletingPathExtension];
+ NSString *ext = [fileName pathExtension];
+ if (ext.length == 0) {
+ path = [NSString stringWithFormat:@"%@%@ (%d)", prefix, name, count];
+ } else {
+ path = [NSString stringWithFormat:@"%@%@ (%d).%@", prefix, name, count, ext];
+ }
+ }
+ [data writeToFile:path atomically:YES];
+ }
+
+ /*
+ // Then pull something from the device using the message body
+ NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
+
+ // Execute some JavaScript using the result?
+ NSString *exec_template = @"set_headline(\"received: %@\");";
+ NSString *exec = [NSString stringWithFormat:exec_template, version];
+ [webView evaluateJavaScript:exec completionHandler:nil];
+ */
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (webView == nil)
+ return;
+
+ if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+
+ }
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", "webViewWebContentProcessDidTerminate");
+}
+
+- (void)webView:(UIWebView *)uiWebView didFailLoadWithError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)uiWebView {
+ if (webView == nil)
+ return;
+ // cf. http://stackoverflow.com/questions/10996028/uiwebview-when-did-a-page-really-finish-loading/15916853#15916853
+ if ([[uiWebView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+ }
+}
+
+- (BOOL)webView:(UIWebView *)uiWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
+{
+ if (webView == nil)
+ return YES;
+
+ NSURL *nsurl = [request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ return NO;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ return NO;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ return NO;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ return NO;
+ } else {
+ if (![self isSetupedCustomHeader:request]) {
+ [uiWebView loadRequest:[self constructionCustomHeader:request]];
+ return NO;
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ return YES;
+ }
+}
+
+- (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ if (!navigationAction.targetFrame.isMainFrame) {
+ [wkWebView loadRequest:navigationAction.request];
+ }
+ return nil;
+}
+
+- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ if (webView == nil) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ NSURL *nsurl = [navigationAction.request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
+ && ![url hasPrefix:@"about:srcdoc"] // for iframe srcdoc attribute
+ && ![url hasPrefix:@"file:"]
+ && ![url hasPrefix:@"http:"]
+ && ![url hasPrefix:@"https:"]) {
+ if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
+ && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else {
+ if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
+ // If the custom header is not attached, give it and make a request again.
+ if (![self isSetupedCustomHeader:[navigationAction request]]) {
+ //NSLog(@"navi ... %@", navigationAction);
+ [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ }
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ // cf. https://stackoverflow.com/questions/37086605/disable-wkwebview-for-opening-links-to-redirect-to-apps-installed-on-my-iphone/76948270#76948270
+ if (!googleAppRedirectionEnabled
+ && [url hasPrefix:@"https://www.google.com/"]
+ && navigationAction.navigationType == WKNavigationTypeLinkActivated) {
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode >= 400) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
+ }
+
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+// alert
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler();
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler();
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// confirm
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(NO);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ completionHandler(YES);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(NO);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// prompt
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(nil);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:prompt
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+ textField.text = defaultText;
+ }];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
+ completionHandler(input);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(nil);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
+{
+ NSURLSessionAuthChallengeDisposition disposition;
+ NSURLCredential *credential;
+ if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
+ disposition = NSURLSessionAuthChallengeUseCredential;
+ credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
+ } else {
+ disposition = NSURLSessionAuthChallengePerformDefaultHandling;
+ credential = nil;
+ }
+ completionHandler(disposition, credential);
+}
+
+- (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
+{
+ // Check for additional custom header.
+ for (NSString *key in [customRequestHeader allKeys]) {
+ if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [customRequestHeader allKeys]) {
+ [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
+{
+ if (webView == nil)
+ return;
+ UIView *view = UnityGetGLViewController().view;
+ CGRect frame = webView.frame;
+ CGRect screen = view.bounds;
+ if (relative) {
+ frame.size.width = floor(screen.size.width * (1.0f - left - right));
+ frame.size.height = floor(screen.size.height * (1.0f - top - bottom));
+ frame.origin.x = floor(screen.size.width * left);
+ frame.origin.y = floor(screen.size.height * top);
+ } else {
+ CGFloat scale = 1.0f / [self getScale:view];
+ frame.size.width = floor(screen.size.width - scale * (left + right));
+ frame.size.height = floor(screen.size.height - scale * (top + bottom));
+ frame.origin.x = floor(scale * left);
+ frame.origin.y = floor(scale * top);
+ }
+ webView.frame = frame;
+}
+
+- (CGFloat)getScale:(UIView *)view
+{
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ return view.window.screen.nativeScale;
+ return view.contentScaleFactor;
+}
+
+- (void)setVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ webView.hidden = visibility ? NO : YES;
+}
+
+- (void)setInteractionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ webView.userInteractionEnabled = enabled;
+}
+
+- (void)setGoogleAppRedirectionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ googleAppRedirectionEnabled = enabled;
+}
+
+- (void)setAlertDialogEnabled:(BOOL)enabled
+{
+ alertDialogEnabled = enabled;
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ [webView setScrollbarsVisibility:visibility];
+}
+
+- (void)setScrollBounceEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ [webView setScrollBounce:enabled];
+}
+
+- (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
+{
+ NSError *err = nil;
+ NSRegularExpression *allow = nil;
+ NSRegularExpression *deny = nil;
+ NSRegularExpression *hook = nil;
+ if (allowPattern == nil || *allowPattern == '\0') {
+ allow = nil;
+ } else {
+ allow
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (denyPattern == nil || *denyPattern == '\0') {
+ deny = nil;
+ } else {
+ deny
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (hookPattern == nil || *hookPattern == '\0') {
+ hook = nil;
+ } else {
+ hook
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ allowRegex = allow;
+ denyRegex = deny;
+ hookRegex = hook;
+ return YES;
+}
+
+- (void)loadURL:(const char *)url
+{
+ if (webView == nil)
+ return;
+ NSString *urlStr = [NSString stringWithUTF8String:url];
+ NSURL *nsurl = [NSURL URLWithString:urlStr];
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
+ [webView load:request];
+}
+
+- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
+{
+ if (webView == nil)
+ return;
+ NSString *htmlStr = [NSString stringWithUTF8String:html];
+ NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
+ NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
+ [webView loadHTML:htmlStr baseURL:baseNSUrl];
+}
+
+- (void)evaluateJS:(const char *)js
+{
+ if (webView == nil)
+ return;
+ NSString *jsStr = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
+}
+
+- (int)progress
+{
+ if (webView == nil)
+ return 0;
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ return (int)([(WKWebView *)webView estimatedProgress] * 100);
+ } else {
+ return 0;
+ }
+}
+
+- (BOOL)canGoBack
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoForward];
+}
+
+- (void)goBack
+{
+ if (webView == nil)
+ return;
+ [webView goBack];
+}
+
+- (void)goForward
+{
+ if (webView == nil)
+ return;
+ [webView goForward];
+}
+
+- (void)reload
+{
+ if (webView == nil)
+ return;
+ [webView reload];
+}
+
+- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *valueString = [NSString stringWithUTF8String:headerValue];
+
+ [customRequestHeader setObject:valueString forKey:keyString];
+}
+
+- (void)removeCustomRequestHeader:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+
+ if ([[customRequestHeader allKeys]containsObject:keyString]) {
+ [customRequestHeader removeObjectForKey:keyString];
+ }
+}
+
+- (void)clearCustomRequestHeader
+{
+ [customRequestHeader removeAllObjects];
+}
+
+- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *result = [customRequestHeader objectForKey:keyString];
+ if (!result) {
+ return NULL;
+ }
+
+ const char *s = [result UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+- (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
+{
+ basicAuthUserName = [NSString stringWithUTF8String:userName];
+ basicAuthPassword = [NSString stringWithUTF8String:password];
+}
+@end
+
+extern "C" {
+ BOOL _CWebViewPlugin_IsInitialized(void *instance);
+ void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius);
+ void _CWebViewPlugin_Destroy(void *instance);
+ void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative);
+ void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
+ BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
+ void _CWebViewPlugin_LoadURL(void *instance, const char *url);
+ void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
+ void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
+ int _CWebViewPlugin_Progress(void *instance);
+ BOOL _CWebViewPlugin_CanGoBack(void *instance);
+ BOOL _CWebViewPlugin_CanGoForward(void *instance);
+ void _CWebViewPlugin_GoBack(void *instance);
+ void _CWebViewPlugin_GoForward(void *instance);
+ void _CWebViewPlugin_Reload(void *instance);
+ void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
+ void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCustomHeader(void *instance);
+ void _CWebViewPlugin_ClearCookie(const char *url, const char *name);
+ void _CWebViewPlugin_ClearCookies();
+ void _CWebViewPlugin_SaveCookies();
+ void _CWebViewPlugin_GetCookies(void *instance, const char *url);
+ const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
+ void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
+ void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
+ void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended);
+}
+
+BOOL _CWebViewPlugin_IsInitialized(void *instance)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin isInitialized];
+}
+
+void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius)
+{
+ WKContentMode wkContentMode = WKContentModeRecommended;
+ switch (contentMode) {
+ case 1:
+ wkContentMode = WKContentModeMobile;
+ break;
+ case 2:
+ wkContentMode = WKContentModeDesktop;
+ break;
+ default:
+ wkContentMode = WKContentModeRecommended;
+ break;
+ }
+ CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode allowsLinkPreview:allowsLinkPreview allowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures radius:radius];
+ [_instances addObject:webViewPlugin];
+ return (__bridge_retained void *)webViewPlugin;
+}
+
+void _CWebViewPlugin_Destroy(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
+ [_instances removeObject:webViewPlugin];
+ [webViewPlugin dispose];
+ webViewPlugin = nil;
+}
+
+void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
+}
+
+void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setInteractionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setGoogleAppRedirectionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAlertDialogEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollbarsVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollBounceEnabled:enabled];
+}
+
+BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
+}
+
+void _CWebViewPlugin_LoadURL(void *instance, const char *url)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadURL:url];
+}
+
+void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadHTML:html baseURL:baseUrl];
+}
+
+void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin evaluateJS:js];
+}
+
+int _CWebViewPlugin_Progress(void *instance)
+{
+ if (instance == NULL)
+ return 0;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin progress];
+}
+
+BOOL _CWebViewPlugin_CanGoBack(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoBack];
+}
+
+BOOL _CWebViewPlugin_CanGoForward(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoForward];
+}
+
+void _CWebViewPlugin_GoBack(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goBack];
+}
+
+void _CWebViewPlugin_GoForward(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goForward];
+}
+
+void _CWebViewPlugin_Reload(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin reload];
+}
+
+void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
+}
+
+void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin removeCustomRequestHeader:headerKey];
+}
+
+void _CWebViewPlugin_ClearCustomHeader(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCustomRequestHeader];
+}
+
+void _CWebViewPlugin_ClearCookie(const char *url, const char *name)
+{
+ [CWebViewPlugin clearCookie:name of:url];
+}
+
+void _CWebViewPlugin_ClearCookies()
+{
+ [CWebViewPlugin clearCookies];
+}
+
+void _CWebViewPlugin_SaveCookies()
+{
+ [CWebViewPlugin saveCookies];
+}
+
+void _CWebViewPlugin_GetCookies(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin getCookies:url];
+}
+
+const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return NULL;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin getCustomRequestHeaderValue:headerKey];
+}
+
+void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setBasicAuthInfo:userName password:password];
+}
+
+void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
+{
+ // no op
+}
+
+void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended)
+{
+ // no op
+}
+#endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
diff --git a/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta b/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta
new file mode 100644
index 00000000..f5e0fb85
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: fc99cbfa2b53248b18d60e327b478581
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib b/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib
new file mode 100644
index 00000000..95777383
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib
@@ -0,0 +1,31 @@
+mergeInto(LibraryManager.library, {
+ _gree_unity_webview_init: function(name) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.init(stringify(name));
+ },
+
+ _gree_unity_webview_setMargins: function (name, left, top, right, bottom) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.setMargins(stringify(name), left, top, right, bottom);
+ },
+
+ _gree_unity_webview_setVisibility: function(name, visible) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.setVisibility(stringify(name), visible);
+ },
+
+ _gree_unity_webview_loadURL: function(name, url) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.loadURL(stringify(name), stringify(url));
+ },
+
+ _gree_unity_webview_evaluateJS: function(name, js) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.evaluateJS(stringify(name), stringify(js));
+ },
+
+ _gree_unity_webview_destroy: function(name) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.destroy(stringify(name));
+ },
+});
diff --git a/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta b/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta
new file mode 100644
index 00000000..2e24b029
--- /dev/null
+++ b/dist/package-nofragment/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 1353be0798ab043d992cd72e4d92970b
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ WebGL: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates.meta b/dist/package-nofragment/Assets/WebGLTemplates.meta
new file mode 100644
index 00000000..1df8df3d
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 396d2c966866e4a8ca47369a69d03109
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020.meta
new file mode 100644
index 00000000..756a586c
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d9e103622e8c14154a1cd918fb92795e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html
new file mode 100644
index 00000000..84f13023
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Unity WebGL Player | {{{ PRODUCT_NAME }}}
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html.meta
new file mode 100644
index 00000000..512f42c4
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7be04fa587d934a5c958c8fc02a10c40
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js
new file mode 100644
index 00000000..6a9c777f
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js
@@ -0,0 +1,103 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .appendTo($('#unity-container'));
+ }
+ var $last = $('.webviewContainer:last');
+ var clonedTop = parseInt($last.css('top')) - 100;
+ var $clone = $last.clone().insertAfter($last).css('top', clonedTop + '%');
+ var $iframe =
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
+ });
+ },
+
+ sendMessage: function (name, message) {
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var container = $('#unity-container');
+ var r = (container.hasClass('unity-desktop')) ? window.devicePixelRatio : 1;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
+ var canvas = $('#unity-canvas');
+ var w1 = canvas.attr('width');
+ var h1 = canvas.attr('height');
+
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w1 - left - right) / w0 * 100;
+ var hp = (h1 - top - bottom) / h0 * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta
new file mode 100644
index 00000000..8ee7ab8c
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5b98600d622f440fab913c56685e11bf
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview.meta
new file mode 100644
index 00000000..7c3eabcf
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 74f24ca1a6cc14b5c8da7e2a8e5de817
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html
new file mode 100644
index 00000000..7474f1c4
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+ Unity Web Player
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html.meta
new file mode 100644
index 00000000..5b8b18d1
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: cd45543727a7e47d88051ca9ab86a6f5
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js
new file mode 100644
index 00000000..64f473c0
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js
@@ -0,0 +1,100 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .appendTo($('#gameContainer'));
+ }
+ var $last = $('.webviewContainer:last');
+ var clonedTop = parseInt($last.css('top')) - 100;
+ var $clone = $last.clone().insertAfter($last).css('top', clonedTop + '%');
+ var $iframe =
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
+ });
+ },
+
+ sendMessage: function (name, message) {
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var container = $('#gameContainer');
+ var r = window.devicePixelRatio;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
+
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w0 - left - right) / w0 * 100;
+ var hp = (h0 - top - bottom) / h0 * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta
new file mode 100644
index 00000000..b421d598
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8de1ecd3ea5954800b53548d8c2e2d70
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates.meta b/dist/package-nofragment/Assets/WebPlayerTemplates.meta
new file mode 100644
index 00000000..d1d66623
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1ec4661d3c10c4d0e8b5e54ce1e46aa5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview.meta b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview.meta
new file mode 100644
index 00000000..06794cb6
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e76c9a30b7f6447eca50079ce4d595ed
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html
new file mode 100644
index 00000000..511e1137
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html
@@ -0,0 +1,136 @@
+
+
+
+
+ Unity Web Player | %UNITY_WEB_NAME%
+ %UNITY_UNITYOBJECT_DEPENDENCIES%
+
+
+
+
+
+
+ %UNITY_BETA_WARNING%
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html.meta b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html.meta
new file mode 100644
index 00000000..fcb667ad
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f6333062aa8e346f2abc78ef7a457580
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png
new file mode 100644
index 00000000..773c2e2d
Binary files /dev/null and b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png differ
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta
new file mode 100644
index 00000000..b1c973ba
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 018355354713f41b2bed252c88e082c7
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js
new file mode 100644
index 00000000..f2ebe5fb
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js
@@ -0,0 +1,99 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ u.getUnity().SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ } else {
+ w.location.replace(href);
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ u.getUnity().SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+ });
+ },
+
+ sendMessage: function (name, message) {
+ u.getUnity().SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var $player = $('#unityPlayer');
+ var width = $player.width();
+ var height = $player.height();
+
+ var lp = left / width * 100;
+ var tp = top / height * 100;
+ var wp = (width - left - right) / width * 100;
+ var hp = (height - top - bottom) / height * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta
new file mode 100644
index 00000000..ac69546e
--- /dev/null
+++ b/dist/package-nofragment/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7bf88e2aa1e624d64b530ad0c2383b9e
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/package.json b/dist/package-nofragment/package.json
new file mode 100644
index 00000000..6366839c
--- /dev/null
+++ b/dist/package-nofragment/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "net.gree.unity-webview",
+ "displayName": "unity-webview",
+ "version": "1.0.0",
+ "unity": "2019.1",
+ "description": "A plugin to display native webview views.",
+ "dependencies": {}
+}
diff --git a/dist/package-nofragment/package.json.meta b/dist/package-nofragment/package.json.meta
new file mode 100644
index 00000000..ab921bc5
--- /dev/null
+++ b/dist/package-nofragment/package.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d863dd473116e4930a3d4f7444365973
+PackageManifestImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package-nofragment/unity-webview.asmdef b/dist/package-nofragment/unity-webview.asmdef
new file mode 100644
index 00000000..de6eaec9
--- /dev/null
+++ b/dist/package-nofragment/unity-webview.asmdef
@@ -0,0 +1,30 @@
+{
+ "name": "unity-webview",
+ "rootNamespace": "",
+ "references": [
+ "Unity.InputSystem"
+ ],
+ "includePlatforms": [
+ "Android",
+ "Editor",
+ "iOS",
+ "macOSStandalone",
+ "WebGL",
+ "WindowsStandalone32",
+ "WindowsStandalone64"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [
+ {
+ "name": "com.unity.inputsystem",
+ "expression": "",
+ "define": "UNITYWEBVIEW_USE_INPUT_SYSTEM"
+ }
+ ],
+ "noEngineReferences": false
+}
diff --git a/dist/package-nofragment/unity-webview.asmdef.meta b/dist/package-nofragment/unity-webview.asmdef.meta
new file mode 100644
index 00000000..102d91cc
--- /dev/null
+++ b/dist/package-nofragment/unity-webview.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: df2a24f83ece042be84d3276a68393ed
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets.meta b/dist/package/Assets.meta
new file mode 100644
index 00000000..cb2d299b
--- /dev/null
+++ b/dist/package/Assets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ad965e05b08a049f2bd5d60258452b36
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins.meta b/dist/package/Assets/Plugins.meta
new file mode 100644
index 00000000..a772e932
--- /dev/null
+++ b/dist/package/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3fab9ef1899dc4bf18586c6a01172d62
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Android.meta b/dist/package/Assets/Plugins/Android.meta
new file mode 100644
index 00000000..a55a2c27
--- /dev/null
+++ b/dist/package/Assets/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dbc2200488e7d45189f9a082caf5d637
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl b/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl
new file mode 100644
index 00000000..d338e7c2
Binary files /dev/null and b/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl differ
diff --git a/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta b/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta
new file mode 100644
index 00000000..18d15d70
--- /dev/null
+++ b/dist/package/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 59b3f9343bdac42318926c7944365bb9
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl b/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl
new file mode 100644
index 00000000..6788d893
Binary files /dev/null and b/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl differ
diff --git a/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta b/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta
new file mode 100644
index 00000000..83c2313f
--- /dev/null
+++ b/dist/package/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ea6387b3379b4458498b339bcd316c7c
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl b/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl
new file mode 100644
index 00000000..cdf16635
Binary files /dev/null and b/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl differ
diff --git a/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl.meta b/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl.meta
new file mode 100644
index 00000000..666d046e
--- /dev/null
+++ b/dist/package/Assets/Plugins/Android/core-1.6.0.aar.tmpl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 70fc6af576ac24f6aa283eecbf393621
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Editor.meta b/dist/package/Assets/Plugins/Editor.meta
new file mode 100644
index 00000000..4afc4293
--- /dev/null
+++ b/dist/package/Assets/Plugins/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5415c2bc4488c42739b36767bc7f8c83
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs b/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs
new file mode 100644
index 00000000..bae62938
--- /dev/null
+++ b/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs
@@ -0,0 +1,568 @@
+#if UNITY_EDITOR
+using System.Collections.Generic;
+using System.Collections;
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Xml;
+using System;
+using UnityEditor.Android;
+#if UNITY_2020_1_OR_NEWER
+using UnityEditor.Build.Reporting;
+#endif
+#if UNITY_2018_1_OR_NEWER
+using UnityEditor.Build;
+#endif
+using UnityEditor.Callbacks;
+using UnityEditor;
+using UnityEngine;
+
+namespace Gree.UnityWebView
+{
+#if UNITY_2020_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuildWithReport, IPostGenerateGradleAndroidProject
+#elif UNITY_2018_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuild, IPostGenerateGradleAndroidProject
+#else
+ public class UnityWebViewPostprocessBuild
+#endif
+ {
+ private static bool nofragment = false;
+
+ //// for android/unity 2018.1 or newer
+ //// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
+ //// cf. https://github.com/Over17/UnityAndroidManifestCallback
+
+#if UNITY_2018_1_OR_NEWER
+#if UNITY_2020_1_OR_NEWER
+ public void OnPreprocessBuild(BuildReport buildReport) {
+ var buildTarget = buildReport.summary.platform;
+#else
+ public void OnPreprocessBuild(BuildTarget buildTarget, string path) {
+#endif
+ if (buildTarget == BuildTarget.Android) {
+ var dev = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ var rel = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ if (!File.Exists(dev) || !File.Exists(rel)) {
+ dev = "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ rel = "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ }
+ var src = (EditorUserBuildSettings.development) ? dev : rel;
+ //Directory.CreateDirectory("Temp/StagingArea/aar");
+ //File.Copy(src, "Temp/StagingArea/aar/WebViewPlugin.aar", true);
+ Directory.CreateDirectory("Assets/Plugins/Android");
+ File.Copy(src, "Assets/Plugins/Android/WebViewPlugin.aar", true);
+ }
+ }
+
+ public void OnPostGenerateGradleAndroidProject(string basePath) {
+ var changed = false;
+ var androidManifest = new AndroidManifest(GetManifestPath(basePath));
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider(basePath) || changed);
+ {
+ var path = GetBuildGradlePath(basePath);
+ var lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n").Split(new[]{'\n'});
+ {
+ var lines = new List();
+ var independencies = false;
+ foreach (var line in lines0) {
+ if (line == "dependencies {") {
+ independencies = true;
+ } else if (independencies && line == "}") {
+ independencies = false;
+ lines.Add(" implementation 'androidx.core:core:1.6.0'");
+ } else if (independencies) {
+ if (line.Contains("implementation(name: 'core")
+ || line.Contains("implementation(name: 'androidx.core.core")
+ || line.Contains("implementation 'androidx.core:core")) {
+ break;
+ }
+ }
+ lines.Add(line);
+ }
+ if (lines.Count > lines0.Length) {
+ File.WriteAllText(path, string.Join("\n", lines) + "\n");
+ }
+ }
+ }
+ {
+ var path = GetGradlePropertiesPath(basePath);
+ var lines0 = "";
+ var lines = "";
+ if (File.Exists(path)) {
+ lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n") + "\n";
+ lines = lines0;
+ }
+ if (!lines.Contains("android.useAndroidX=true")) {
+ lines += "android.useAndroidX=true\n";
+ }
+ if (!lines.Contains("android.enableJetifier=true")) {
+ lines += "android.enableJetifier=true\n";
+ }
+ if (lines != lines0) {
+ File.WriteAllText(path, lines);
+ }
+ }
+ }
+ changed = (androidManifest.SetExported(true) || changed);
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ changed = (androidManifest.AddCamera() || changed);
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ changed = (androidManifest.AddMicrophone() || changed);
+#endif
+ }
+ if (changed) {
+ androidManifest.Save();
+ Debug.Log("unitywebview: adjusted AndroidManifest.xml.");
+ }
+ }
+#endif
+
+ public int callbackOrder {
+ get {
+ return 1;
+ }
+ }
+
+ private string GetManifestPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
+ return pathBuilder.ToString();
+ }
+
+ private string GetBuildGradlePath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("build.gradle");
+ return pathBuilder.ToString();
+ }
+
+ private string GetGradlePropertiesPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ if (basePath.EndsWith("unityLibrary")) {
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("..");
+ }
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("gradle.properties");
+ return pathBuilder.ToString();
+ }
+
+ //// for others
+
+ [PostProcessBuild(100)]
+ public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
+#if UNITY_2018_1_OR_NEWER
+ try {
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar");
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar.meta");
+ Directory.Delete("Assets/Plugins/Android");
+ File.Delete("Assets/Plugins/Android.meta");
+ Directory.Delete("Assets/Plugins");
+ File.Delete("Assets/Plugins.meta");
+ } catch (Exception) {
+ }
+#else
+ if (buildTarget == BuildTarget.Android) {
+ string manifest = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
+ if (!File.Exists(manifest)) {
+ string manifest0 = Path.Combine(Application.dataPath, "../Temp/StagingArea/AndroidManifest-main.xml");
+ if (!File.Exists(manifest0)) {
+ Debug.LogError("unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again.");
+ return;
+ } else {
+ File.Copy(manifest0, manifest, true);
+ }
+ }
+ var changed = false;
+ if (EditorUserBuildSettings.development) {
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
+ } else {
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
+ }
+ var androidManifest = new AndroidManifest(manifest);
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider("Assets/Plugins/Android") || changed);
+ var files = Directory.GetFiles("Assets/Plugins/Android/");
+ var found = false;
+ foreach (var file in files) {
+ if (Regex.IsMatch(file, @"^Assets/Plugins/Android/(androidx\.core\.)?core-.*.aar$")) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ foreach (var file in files) {
+ var match = Regex.Match(file, @"^Assets/Plugins/Android/(core.*.aar).tmpl$");
+ if (match.Success) {
+ var name = match.Groups[1].Value;
+ File.Copy(file, "Assets/Plugins/Android/" + name, true);
+ break;
+ }
+ }
+ }
+ }
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ changed = (androidManifest.AddCamera() || changed);
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ changed = (androidManifest.AddMicrophone() || changed);
+#endif
+ }
+#if UNITY_5_6_0 || UNITY_5_6_1
+ changed = (androidManifest.SetActivityName("net.gree.unitywebview.CUnityPlayerActivity") || changed);
+#endif
+ if (changed) {
+ androidManifest.Save();
+ Debug.LogError("unitywebview: adjusted AndroidManifest.xml and/or WebView.aar. Please rebuild the app.");
+ }
+ }
+#endif
+ if (buildTarget == BuildTarget.iOS) {
+ string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
+ var type = Type.GetType("UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode");
+ if (type == null)
+ {
+ Debug.LogError("unitywebview: failed to get PBXProject. please install iOS build support.");
+ return;
+ }
+ var src = File.ReadAllText(projPath);
+ //dynamic proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ var proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ //proj.ReadFromString(src);
+ {
+ var method = type.GetMethod("ReadFromString");
+ method.Invoke(proj, new object[]{src});
+ }
+ var target = "";
+#if UNITY_2019_3_OR_NEWER
+ //target = proj.GetUnityFrameworkTargetGuid();
+ {
+ var method = type.GetMethod("GetUnityFrameworkTargetGuid");
+ target = (string)method.Invoke(proj, null);
+ }
+#else
+ //target = proj.TargetGuidByName("Unity-iPhone");
+ {
+ var method = type.GetMethod("TargetGuidByName");
+ target = (string)method.Invoke(proj, new object[]{"Unity-iPhone"});
+ }
+#endif
+ //proj.AddFrameworkToProject(target, "WebKit.framework", false);
+ {
+ var method = type.GetMethod("AddFrameworkToProject");
+ method.Invoke(proj, new object[]{target, "WebKit.framework", false});
+ }
+ var cflags = "";
+ if (EditorUserBuildSettings.development) {
+ cflags += " -DUNITYWEBVIEW_DEVELOPMENT";
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS";
+#endif
+ cflags = cflags.Trim();
+ if (!string.IsNullOrEmpty(cflags)) {
+ // proj.AddBuildProperty(target, "OTHER_LDFLAGS", cflags);
+ var method = type.GetMethod("AddBuildProperty", new Type[]{typeof(string), typeof(string), typeof(string)});
+ method.Invoke(proj, new object[]{target, "OTHER_CFLAGS", cflags});
+ }
+ var dst = "";
+ //dst = proj.WriteToString();
+ {
+ var method = type.GetMethod("WriteToString");
+ dst = (string)method.Invoke(proj, null);
+ }
+ File.WriteAllText(projPath, dst);
+ }
+ }
+ }
+
+ internal class AndroidXmlDocument : XmlDocument {
+ private string m_Path;
+ protected XmlNamespaceManager nsMgr;
+ public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
+
+ public AndroidXmlDocument(string path) {
+ m_Path = path;
+ using (var reader = new XmlTextReader(m_Path)) {
+ reader.Read();
+ Load(reader);
+ }
+ nsMgr = new XmlNamespaceManager(NameTable);
+ nsMgr.AddNamespace("android", AndroidXmlNamespace);
+ }
+
+ public string Save() {
+ return SaveAs(m_Path);
+ }
+
+ public string SaveAs(string path) {
+ using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) {
+ writer.Formatting = Formatting.Indented;
+ Save(writer);
+ }
+ return path;
+ }
+ }
+
+ internal class AndroidManifest : AndroidXmlDocument {
+ private readonly XmlElement ManifestElement;
+ private readonly XmlElement ApplicationElement;
+
+ public AndroidManifest(string path) : base(path) {
+ ManifestElement = SelectSingleNode("/manifest") as XmlElement;
+ ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
+ }
+
+ private XmlAttribute CreateAndroidAttribute(string key, string value) {
+ XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
+ attr.Value = value;
+ return attr;
+ }
+
+ internal XmlNode GetActivityWithLaunchIntent() {
+ return
+ SelectSingleNode(
+ "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and "
+ + "intent-filter/category/@android:name='android.intent.category.LAUNCHER']",
+ nsMgr);
+ }
+
+ internal bool SetUsesCleartextTraffic(bool enabled) {
+ // android:usesCleartextTraffic
+ bool changed = false;
+ if (ApplicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ ApplicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ // for api level 33
+ internal bool SetExported(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("exported", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("exported", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetWindowSoftInputMode(string mode) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("windowSoftInputMode", AndroidXmlNamespace) != mode) {
+ activity.SetAttribute("windowSoftInputMode", AndroidXmlNamespace, mode);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetHardwareAccelerated(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetActivityName(string name) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("name", AndroidXmlNamespace) != name) {
+ activity.SetAttribute("name", AndroidXmlNamespace, name);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddFileProvider(string basePath) {
+ bool changed = false;
+ var authorities = PlayerSettings.applicationIdentifier + ".unitywebview.fileprovider";
+ if (SelectNodes("/manifest/application/provider[@android:authorities='" + authorities + "']", nsMgr).Count == 0) {
+ var elem = CreateElement("provider");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "androidx.core.content.FileProvider"));
+ elem.Attributes.Append(CreateAndroidAttribute("authorities", authorities));
+ elem.Attributes.Append(CreateAndroidAttribute("exported", "false"));
+ elem.Attributes.Append(CreateAndroidAttribute("grantUriPermissions", "true"));
+ var meta = CreateElement("meta-data");
+ meta.Attributes.Append(CreateAndroidAttribute("name", "android.support.FILE_PROVIDER_PATHS"));
+ meta.Attributes.Append(CreateAndroidAttribute("resource", "@xml/unitywebview_file_provider_paths"));
+ elem.AppendChild(meta);
+ ApplicationElement.AppendChild(elem);
+ changed = true;
+ var xml = GetFileProviderSettingPath(basePath);
+ if (!File.Exists(xml)) {
+ Directory.CreateDirectory(Path.GetDirectoryName(xml));
+ File.WriteAllText(
+ xml,
+ "\n" +
+ " \n" +
+ "\n");
+ }
+ }
+ return changed;
+ }
+
+ private string GetFileProviderSettingPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("res");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("xml");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("unitywebview_file_provider_paths.xml");
+ return pathBuilder.ToString();
+ }
+
+ internal bool AddCamera() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/data-storage/shared/media#media-location-permission
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.ACCESS_MEDIA_LOCATION']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.ACCESS_MEDIA_LOCATION"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.IMAGE_CAPTURE']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.IMAGE_CAPTURE"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddGallery() {
+ bool changed = false;
+ // for api level 33
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_IMAGES']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_IMAGES"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_VIDEO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_VIDEO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.GET_CONTENT']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.GET_CONTENT"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddMicrophone() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://github.com/gree/unity-webview/issues/679
+ // cf. https://github.com/fluttercommunity/flutter_webview_plugin/issues/138#issuecomment-559307558
+ // cf. https://stackoverflow.com/questions/38917751/webview-webrtc-not-working/68024032#68024032
+ // cf. https://stackoverflow.com/questions/40236925/allowing-microphone-accesspermission-in-webview-android-studio-java/47410311#47410311
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MODIFY_AUDIO_SETTINGS']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MODIFY_AUDIO_SETTINGS"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.RECORD_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.RECORD_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ return changed;
+ }
+ }
+}
+#endif
diff --git a/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta b/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta
new file mode 100644
index 00000000..2d862b52
--- /dev/null
+++ b/dist/package/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b2f5f306eb6e4afcbc074e6efccc188
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle.meta b/dist/package/Assets/Plugins/WebView.bundle.meta
new file mode 100644
index 00000000..42a688b6
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle.meta
@@ -0,0 +1,58 @@
+fileFormatVersion: 2
+guid: 60e7bf38137eb4950b2f02b7d57c1ad3
+folderAsset: yes
+PluginImporter:
+ serializedVersion: 1
+ iconMap: {}
+ executionOrder: {}
+ isPreloaded: 0
+ platformData:
+ Android:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ Any:
+ enabled: 0
+ settings: {}
+ Editor:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: OSX
+ Linux:
+ enabled: 0
+ settings:
+ CPU: x86
+ Linux64:
+ enabled: 0
+ settings:
+ CPU: x86_64
+ OSXIntel:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ OSXIntel64:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ OSXUniversal:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ Win:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ Win64:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ iOS:
+ enabled: 0
+ settings:
+ CompileFlags:
+ FrameworkDependencies:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents.meta
new file mode 100644
index 00000000..5839b4ea
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ee2bc92b52f924630bfd4aff89395583
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist b/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist
new file mode 100644
index 00000000..cf08a6fa
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist
@@ -0,0 +1,48 @@
+
+
+
+
+ BuildMachineOSBuild
+ 25F84
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ WebView
+ CFBundleIdentifier
+ net.gree.unitywebview.WebView
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ WebView
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleVersion
+ 1
+ DTCompiler
+ com.apple.compilers.llvm.clang.1_0
+ DTPlatformBuild
+ 25F70
+ DTPlatformName
+ macosx
+ DTPlatformVersion
+ 26.5
+ DTSDKBuild
+ 25F70
+ DTSDKName
+ macosx26.5
+ DTXcode
+ 2650
+ DTXcodeBuild
+ 17F42
+ LSMinimumSystemVersion
+ 10.13
+
+
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta
new file mode 100644
index 00000000..683244d3
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/Info.plist.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f5cac4b018fc441519472619c00b4a19
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS.meta
new file mode 100644
index 00000000..402df322
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7583463a0bdf148919eb1819ff8790ab
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView
new file mode 100755
index 00000000..01b503aa
Binary files /dev/null and b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView differ
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta
new file mode 100644
index 00000000..88fa0aa7
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3472ba57f3d2c40728fe4fa686337555
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources.meta
new file mode 100644
index 00000000..f28fb9be
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b8d99a8979b8445dc8d524538cf76521
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings
new file mode 100644
index 00000000..5e45963c
Binary files /dev/null and b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings differ
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta
new file mode 100644
index 00000000..da50d44b
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6d60a4326049747b58f27bc930bcd7f4
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta
new file mode 100644
index 00000000..3a521ad0
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b4f429b11407f476894734317eaa0089
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources
new file mode 100644
index 00000000..f4d2e431
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,128 @@
+
+
+
+
+ files
+
+ Resources/InfoPlist.strings
+
+ MiLKDDnrUKr4EmuvhS5VQwxHGK8=
+
+
+ files2
+
+ Resources/InfoPlist.strings
+
+ hash2
+
+ Oc8u4Ht7Mz58F50L9NeYpbcq9qTlhPUeZCcDu/pPyCg=
+
+
+
+ rules
+
+ ^Resources/
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^Resources/
+
+ weight
+ 20
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta
new file mode 100644
index 00000000..82f59825
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7484b160ebd7c40bd9c42038fa7b69a8
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/WebViewObject.cs b/dist/package/Assets/Plugins/WebViewObject.cs
new file mode 100644
index 00000000..eddbcd51
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebViewObject.cs
@@ -0,0 +1,2213 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+using UnityEngine;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+#if UNITY_2018_4_OR_NEWER
+using UnityEngine.Networking;
+#endif
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+using UnityEngine.InputSystem;
+#endif
+#endif
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+using System.IO;
+using System.Text.RegularExpressions;
+using UnityEngine.EventSystems;
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_ANDROID
+using UnityEngine.Android;
+#endif
+
+using Callback = System.Action;
+
+namespace Gree.UnityWebView
+{
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ public class UnitySendMessageDispatcher
+ {
+ public static void Dispatch(string name, string method, string message)
+ {
+ GameObject obj = GameObject.Find(name);
+ if (obj != null)
+ obj.SendMessage(method, message);
+ }
+ }
+#endif
+
+ public class WebViewObject : MonoBehaviour
+ {
+ Callback onJS;
+ Callback onError;
+ Callback onHttpError;
+ Callback onStarted;
+ Callback onLoaded;
+ Callback onHooked;
+ Callback onCookies;
+ bool paused;
+ bool visibility;
+ bool alertDialogEnabled;
+ bool scrollBounceEnabled;
+ int mMarginLeft;
+ int mMarginTop;
+ int mMarginRight;
+ int mMarginBottom;
+ bool mMarginRelative;
+ float mMarginLeftComputed;
+ float mMarginTopComputed;
+ float mMarginRightComputed;
+ float mMarginBottomComputed;
+ bool mMarginRelativeComputed;
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+#if UNITY_2018_2_OR_NEWER
+#else
+ byte[] textureDataBuffer;
+#endif
+ string inputString = "";
+ bool hasFocus;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+ byte[] textureDataBuffer;
+ string inputString = "";
+ bool hasFocus;
+#elif UNITY_IPHONE
+ IntPtr webView;
+#elif UNITY_ANDROID
+ AndroidJavaObject webView;
+
+ bool mVisibility;
+ int mKeyboardVisibleHeight;
+ float mResumedTimestamp;
+ int mLastScreenHeight;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ float androidNetworkReachabilityCheckT0 = -1.0f;
+ NetworkReachability? androidNetworkReachability0 = null;
+#endif
+ bool mAllowVideoCapture;
+ bool mAllowAudioCapture;
+
+ void OnApplicationPause(bool paused)
+ {
+ this.paused = paused;
+ if (webView == null)
+ return;
+ // if (!paused && mKeyboardVisibleHeight > 0)
+ // {
+ // webView.Call("SetVisibility", false);
+ // mResumedTimestamp = Time.realtimeSinceStartup;
+ // }
+ webView.Call("OnApplicationPause", paused);
+ }
+
+ void Update()
+ {
+ // NOTE:
+ //
+ // When OnApplicationPause(true) is called and the app is in closing, webView.Call(...)
+ // after that could cause crashes because underlying java instances were closed.
+ //
+ // This has not been cleary confirmed yet. However, as Update() is called once after
+ // OnApplicationPause(true), it is likely correct.
+ //
+ // Base on this assumption, we do nothing here if the app is paused.
+ //
+ // cf. https://github.com/gree/unity-webview/issues/991#issuecomment-1776628648
+ // cf. https://docs.unity3d.com/2020.3/Documentation/Manual/ExecutionOrder.html
+ //
+ // In between frames
+ //
+ // * OnApplicationPause: This is called at the end of the frame where the pause is detected,
+ // effectively between the normal frame updates. One extra frame will be issued after
+ // OnApplicationPause is called to allow the game to show graphics that indicate the
+ // paused state.
+ //
+ if (paused)
+ return;
+ if (webView == null)
+ return;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ var t = Time.time;
+ if (t - 1.0f >= androidNetworkReachabilityCheckT0)
+ {
+ androidNetworkReachabilityCheckT0 = t;
+ var androidNetworkReachability = Application.internetReachability;
+ if (androidNetworkReachability0 != androidNetworkReachability)
+ {
+ androidNetworkReachability0 = androidNetworkReachability;
+ webView.Call("SetNetworkAvailable", androidNetworkReachability != NetworkReachability.NotReachable);
+ }
+ }
+#endif
+ if (mResumedTimestamp != 0.0f && Time.realtimeSinceStartup - mResumedTimestamp > 0.5f)
+ {
+ mResumedTimestamp = 0.0f;
+ webView.Call("SetVisibility", mVisibility);
+ }
+ if (Screen.height != mLastScreenHeight)
+ {
+ mLastScreenHeight = Screen.height;
+ webView.Call("EvaluateJS", "(function() {var e = document.activeElement; if (e != null && e.tagName.toLowerCase() != 'body') {e.blur(); e.focus();}})()");
+ }
+ for (;;) {
+ if (webView == null)
+ break;
+ var s = webView.Call("GetMessage");
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ case "SetKeyboardVisible":
+ SetKeyboardVisible(s.Substring(i + 1));
+ break;
+ case "RequestFileChooserPermissions":
+ RequestFileChooserPermissions();
+ break;
+ }
+ }
+ }
+
+ /// Called from Java native plugin to set when the keyboard is opened
+ public void SetKeyboardVisible(string keyboardVisibleHeight)
+ {
+ if (BottomAdjustmentDisabled())
+ {
+ return;
+ }
+ var keyboardVisibleHeight0 = mKeyboardVisibleHeight;
+ var keyboardVisibleHeight1 = Int32.Parse(keyboardVisibleHeight);
+ if (keyboardVisibleHeight0 != keyboardVisibleHeight1)
+ {
+ mKeyboardVisibleHeight = keyboardVisibleHeight1;
+ SetMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom, mMarginRelative);
+ EvaluateJS("setTimeout(function(){if(document&&document.activeElement){document.activeElement.scrollIntoView();}}, 200);");
+ }
+ }
+
+ /// Called from Java native plugin to request permissions for the file chooser.
+ public void RequestFileChooserPermissions()
+ {
+ var permissions = new List();
+ using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
+ {
+ if (version.GetStatic("SDK_INT") >= 33)
+ {
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_IMAGES");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_VIDEO");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_AUDIO");
+ }
+ }
+ else
+ {
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
+ {
+ permissions.Add(Permission.ExternalStorageRead);
+ }
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
+ {
+ permissions.Add(Permission.ExternalStorageWrite);
+ }
+ }
+ }
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && mAllowVideoCapture)
+ {
+ permissions.Add(Permission.Camera);
+ }
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && mAllowAudioCapture)
+ {
+ permissions.Add(Permission.Microphone);
+ }
+#endif
+ if (permissions.Count > 0)
+ {
+#if UNITY_2020_2_OR_NEWER
+ var grantedCount = 0;
+ var deniedCount = 0;
+ var callbacks = new PermissionCallbacks();
+ callbacks.PermissionGranted += (permission) =>
+ {
+ grantedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDenied += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDeniedAndDontAskAgain += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ Permission.RequestUserPermissions(permissions.ToArray(), callbacks);
+#else
+ StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray()));
+#endif
+ }
+ else
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(true));
+ }
+ }
+
+#if UNITY_2020_2_OR_NEWER
+#else
+ int mRequestPermissionPhase;
+
+ IEnumerator RequestFileChooserPermissionsCoroutine(string[] permissions)
+ {
+ foreach (var permission in permissions)
+ {
+ mRequestPermissionPhase = 0;
+ Permission.RequestUserPermission(permission);
+ // waiting permission dialog that may not be opened.
+ for (var i = 0; i < 8 && mRequestPermissionPhase == 0; i++)
+ {
+ yield return new WaitForSeconds(0.25f);
+ }
+ if (mRequestPermissionPhase == 0)
+ {
+ // permission dialog was not opened.
+ continue;
+ }
+ while (mRequestPermissionPhase == 1)
+ {
+ yield return new WaitForSeconds(0.3f);
+ }
+ }
+ yield return new WaitForSeconds(0.3f);
+ var granted = 0;
+ foreach (var permission in permissions)
+ {
+ if (Permission.HasUserAuthorizedPermission(permission))
+ {
+ granted++;
+ }
+ }
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(granted == permissions.Length));
+ }
+
+ void OnApplicationFocus(bool hasFocus)
+ {
+ if (hasFocus)
+ {
+ if (mRequestPermissionPhase == 1)
+ {
+ mRequestPermissionPhase = 2;
+ }
+ }
+ else
+ {
+ if (mRequestPermissionPhase == 0)
+ {
+ mRequestPermissionPhase = 1;
+ }
+ }
+ }
+#endif
+
+ private IEnumerator CallOnRequestFileChooserPermissionsResult(bool granted)
+ {
+ for (var i = 0; i < 3; i++)
+ {
+ yield return null;
+ }
+ webView.Call("OnRequestFileChooserPermissionsResult", granted);
+ }
+
+ public int AdjustBottomMargin(int bottom)
+ {
+ if (BottomAdjustmentDisabled())
+ {
+ return bottom;
+ }
+ else if (mKeyboardVisibleHeight <= 0)
+ {
+ return bottom;
+ }
+ else
+ {
+ int keyboardHeight = 0;
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
+ {
+ if (view.Call("getGlobalVisibleRect", rect))
+ {
+ int h0 = rect.Get("bottom");
+ view.Call("getWindowVisibleDisplayFrame", rect);
+ int h1 = rect.Get("bottom");
+ keyboardHeight = h0 - h1;
+ }
+ }
+ return (bottom > keyboardHeight) ? bottom : keyboardHeight;
+ }
+ }
+
+ private bool BottomAdjustmentDisabled()
+ {
+#if UNITYWEBVIEW_ANDROID_FORCE_MARGIN_ADJUSTMENT_FOR_KEYBOARD
+ return false;
+#else
+ return
+ !Screen.fullScreen
+ || ((Screen.autorotateToLandscapeLeft || Screen.autorotateToLandscapeRight)
+ && (Screen.autorotateToPortrait || Screen.autorotateToPortraitUpsideDown));
+#endif
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ IntPtr webView;
+#else
+ IntPtr webView;
+#endif
+
+ void Awake()
+ {
+ alertDialogEnabled = true;
+ scrollBounceEnabled = true;
+ mMarginLeftComputed = -9999;
+ mMarginTopComputed = -9999;
+ mMarginRightComputed = -9999;
+ mMarginBottomComputed = -9999;
+ }
+
+ public bool IsKeyboardVisible
+ {
+ get
+ {
+#if !UNITY_EDITOR && UNITY_ANDROID
+ return mKeyboardVisibleHeight > 0;
+#elif !UNITY_EDITOR && UNITY_IPHONE
+ return TouchScreenKeyboard.visible;
+#else
+ return false;
+#endif
+ }
+ }
+
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+ void OnEnable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput += OnTextInput;
+ }
+ }
+
+ void OnDisable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput -= OnTextInput;
+ }
+ }
+
+ void OnTextInput(char ch)
+ {
+ if (hasFocus)
+ {
+ inputString += ch;
+ }
+ }
+#endif
+#endif
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_InitStatic(
+ bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(
+ string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(
+ IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_BitmapARGB(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_InitStatic(bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+#elif UNITY_IPHONE
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, string ua, bool enableWKWebView, int wkContentMode, bool wkAllowsLinkPreview, bool wkAllowsBackForwardNavigationGestures, int radius);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetMargins(
+ IntPtr instance, float left, float top, float right, float bottom, bool relative);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollbarsVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetAlertDialogEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollBounceEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetInteractionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("__Internal")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetBasicAuthInfo(IntPtr instance, string userName, string password);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCache(IntPtr instance, bool includeDiskFiles);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetSuspended(IntPtr instance, bool suspended);
+#elif UNITY_WEBGL
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_init(string name);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setMargins(string name, int left, int top, int right, int bottom);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setVisibility(string name, bool visible);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_loadURL(string name, string url);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_evaluateJS(string name, string js);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_destroy(string name);
+#endif
+
+ public static bool IsWebViewAvailable()
+ {
+#if !UNITY_EDITOR && UNITY_ANDROID
+ using (var plugin = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin"))
+ {
+ return plugin.CallStatic("IsWebViewAvailable");
+ }
+#else
+ return true;
+#endif
+ }
+
+ public bool IsInitialized()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Call("IsInitialized");
+#endif
+ }
+
+ public void Init(
+ Callback cb = null,
+ Callback err = null,
+ Callback httpErr = null,
+ Callback ld = null,
+ Callback started = null,
+ Callback hooked = null,
+ Callback cookies = null,
+ bool transparent = false,
+ bool zoom = true,
+ string ua = "",
+ int radius = 0,
+ // android
+ int androidForceDarkMode = 0, // 0: follow system setting, 1: force dark off, 2: force dark on
+ // ios
+ bool enableWKWebView = true,
+ int wkContentMode = 0, // 0: recommended, 1: mobile, 2: desktop
+ bool wkAllowsLinkPreview = true,
+ bool wkAllowsBackForwardNavigationGestures = true,
+ // editor
+ bool separated = false)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.OSXEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal);
+#endif
+ onJS = cb;
+ onError = err;
+ onHttpError = httpErr;
+ onStarted = started;
+ onLoaded = ld;
+ onHooked = hooked;
+ onCookies = cookies;
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_init(name);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.init", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ Debug.LogError("Webview is not supported on this platform.");
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.WindowsEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11);
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ {
+ var uri = new Uri(_CWebViewPlugin_GetAppPath());
+ var info = File.ReadAllText(uri.LocalPath + "Contents/Info.plist");
+ if (Regex.IsMatch(info, @"CFBundleGetInfoString\s*Unity version [5-9]\.[3-9]")
+ && !Regex.IsMatch(info, @"NSAppTransportSecurity\s*\s*NSAllowsArbitraryLoads\s*\s*")) {
+ Debug.LogWarning("WebViewObject: NSAppTransportSecurity isn't configured to allow HTTP. If you need to allow any HTTP access, please shutdown Unity and invoke:\n/usr/libexec/PlistBuddy -c \"Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true\" /Applications/Unity/Unity.app/Contents/Info.plist");
+ }
+ }
+#if UNITY_EDITOR_OSX
+ // if (string.IsNullOrEmpty(ua)) {
+ // ua = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
+ // }
+#endif
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
+#elif UNITY_IPHONE
+ webView = _CWebViewPlugin_Init(name, transparent, zoom, ua, enableWKWebView, wkContentMode, wkAllowsLinkPreview, wkAllowsBackForwardNavigationGestures, radius);
+#elif UNITY_ANDROID
+ webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin");
+#if UNITY_2021_1_OR_NEWER
+ webView.SetStatic("forceBringToFront", true);
+#endif
+ webView.Call("Init", name, transparent, zoom, androidForceDarkMode, ua, radius);
+#else
+ Debug.LogError("Webview is not supported on this platform.");
+#endif
+ }
+
+ protected virtual void OnDestroy()
+ {
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_destroy(name);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.destroy", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (bg != null)
+ {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ var ptr = webView;
+ webView = IntPtr.Zero;
+ _CWebViewPlugin_Destroy(ptr);
+ Destroy(texture);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (bg != null) {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
+ Destroy(texture);
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Destroy");
+ webView.Dispose();
+ webView = null;
+#endif
+ }
+
+ public void Pause()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ // NOTE: this suspends media playback only.
+ if (webView == null)
+ return;
+ _CWebViewPlugin_SetSuspended(webView, true);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Pause");
+#endif
+ }
+
+ public void Resume()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ // NOTE: this resumes media playback only.
+ _CWebViewPlugin_SetSuspended(webView, false);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Resume");
+#endif
+ }
+
+ // Use this function instead of SetMargins to easily set up a centered window
+ // NOTE: for historical reasons, `center` means the lower left corner and positive y values extend up.
+ public void SetCenterPositionWithScale(Vector2 center, Vector2 scale)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
+#else
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
+#endif
+ }
+
+ public void SetMargins(int left, int top, int right, int bottom, bool relative = false)
+ {
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+#endif
+
+ mMarginLeft = left;
+ mMarginTop = top;
+ mMarginRight = right;
+ mMarginBottom = bottom;
+ mMarginRelative = relative;
+ float ml, mt, mr, mb;
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_IPHONE
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ ml = left / w;
+ mt = top / h;
+ mr = right / w;
+ mb = bottom / h;
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+ }
+#elif UNITY_ANDROID
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ int iw = Display.main.systemWidth;
+ int ih = Display.main.systemHeight;
+ if (!Screen.fullScreen)
+ {
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
+ {
+ view.Call("getDrawingRect", rect);
+ iw = rect.Call("width");
+ ih = rect.Call("height");
+ }
+ }
+ ml = left / w * iw;
+ mt = top / h * ih;
+ mr = right / w * iw;
+ mb = AdjustBottomMargin((int)(bottom / h * ih));
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = AdjustBottomMargin(bottom);
+ }
+#endif
+ bool r = relative;
+
+ if (ml == mMarginLeftComputed
+ && mt == mMarginTopComputed
+ && mr == mMarginRightComputed
+ && mb == mMarginBottomComputed
+ && r == mMarginRelativeComputed)
+ {
+ return;
+ }
+ mMarginLeftComputed = ml;
+ mMarginTopComputed = mt;
+ mMarginRightComputed = mr;
+ mMarginBottomComputed = mb;
+ mMarginRelativeComputed = r;
+
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.setMargins", name, (int)ml, (int)mt, (int)mr, (int)mb);
+#elif UNITY_WEBGL && !UNITY_EDITOR
+ _gree_unity_webview_setMargins(name, (int)ml, (int)mt, (int)mr, (int)mb);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
+#elif UNITY_IPHONE
+ _CWebViewPlugin_SetMargins(webView, ml, mt, mr, mb, r);
+#elif UNITY_ANDROID
+ webView.Call("SetMargins", (int)ml, (int)mt, (int)mr, (int)mb);
+#endif
+ }
+
+ public void SetVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (bg != null)
+ {
+ bg.gameObject.SetActive(v);
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#endif
+ if (GetVisibility() && !v)
+ {
+ EvaluateJS("if (document && document.activeElement) document.activeElement.blur();");
+ }
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_setVisibility(name, v);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.setVisibility", name, v);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ mVisibility = v;
+ webView.Call("SetVisibility", v);
+#endif
+ visibility = v;
+ }
+
+ public bool GetVisibility()
+ {
+ return visibility;
+ }
+
+ public void SetScrollbarsVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollbarsVisibility(webView, v);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetScrollbarsVisibility", v);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetInteractionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetInteractionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetInteractionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetGoogleAppRedirectionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetGoogleAppRedirectionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetGoogleAppRedirectionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetAlertDialogEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetAlertDialogEnabled(webView, e);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetAlertDialogEnabled", e);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ alertDialogEnabled = e;
+ }
+
+ public bool GetAlertDialogEnabled()
+ {
+ return alertDialogEnabled;
+ }
+
+ public void SetScrollBounceEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollBounceEnabled(webView, e);
+#elif UNITY_ANDROID
+ // TODO: UNSUPPORTED
+#else
+ // TODO: UNSUPPORTED
+#endif
+ scrollBounceEnabled = e;
+ }
+
+ public bool GetScrollBounceEnabled()
+ {
+ return scrollBounceEnabled;
+ }
+
+ public void SetCameraAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (webView == null)
+ return;
+ webView.Call("SetCameraAccess", allowed);
+ mAllowVideoCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetMicrophoneAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (webView == null)
+ return;
+ webView.Call("SetMicrophoneAccess", allowed);
+ mAllowAudioCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public bool SetURLPattern(string allowPattern, string denyPattern, string hookPattern)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Call("SetURLPattern", allowPattern, denyPattern, hookPattern);
+#endif
+ }
+
+ public void LoadURL(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ return;
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_loadURL(name, url);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.loadURL", name, url);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("LoadURL", url);
+#endif
+ }
+
+ public void LoadHTML(string html, string baseUrl)
+ {
+ if (string.IsNullOrEmpty(html))
+ return;
+ if (string.IsNullOrEmpty(baseUrl))
+ baseUrl = "";
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("LoadHTML", html, baseUrl);
+#endif
+ }
+
+ public void EvaluateJS(string js)
+ {
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_evaluateJS(name, js);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.evaluateJS", name, js);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("EvaluateJS", js);
+#endif
+ }
+
+ public int Progress()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return 0;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return 0;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return 0;
+ return webView.Get("progress");
+#endif
+ }
+
+ public bool CanGoBack()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Get("canGoBack");
+#endif
+ }
+
+ public bool CanGoForward()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Get("canGoForward");
+#endif
+ }
+
+ public void GoBack()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("GoBack");
+#endif
+ }
+
+ public void GoForward()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("GoForward");
+#endif
+ }
+
+ public void Reload()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Reload");
+#endif
+ }
+
+ public void CallOnError(string error)
+ {
+ if (onError != null)
+ {
+ onError(error);
+ }
+ }
+
+ public void CallOnHttpError(string error)
+ {
+ if (onHttpError != null)
+ {
+ onHttpError(error);
+ }
+ }
+
+ public void CallOnStarted(string url)
+ {
+ if (onStarted != null)
+ {
+ onStarted(url);
+ }
+ }
+
+ public void CallOnLoaded(string url)
+ {
+ if (onLoaded != null)
+ {
+ onLoaded(url);
+ }
+ }
+
+ public void CallFromJS(string message)
+ {
+ if (onJS != null)
+ {
+#if !UNITY_ANDROID
+#if UNITY_2018_4_OR_NEWER
+ message = UnityWebRequest.UnEscapeURL(message);
+#else // UNITY_2018_4_OR_NEWER
+ message = WWW.UnEscapeURL(message);
+#endif // UNITY_2018_4_OR_NEWER
+#endif // !UNITY_ANDROID
+ onJS(message);
+ }
+ }
+
+ public void CallOnHooked(string message)
+ {
+ if (onHooked != null)
+ {
+#if !UNITY_ANDROID
+#if UNITY_2018_4_OR_NEWER
+ message = UnityWebRequest.UnEscapeURL(message);
+#else // UNITY_2018_4_OR_NEWER
+ message = WWW.UnEscapeURL(message);
+#endif // UNITY_2018_4_OR_NEWER
+#endif // !UNITY_ANDROID
+ onHooked(message);
+ }
+ }
+
+ public void CallOnCookies(string cookies)
+ {
+ if (onCookies != null)
+ {
+ onCookies(cookies);
+ }
+ }
+
+ public void AddCustomHeader(string headerKey, string headerValue)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("AddCustomHeader", headerKey, headerValue);
+#endif
+ }
+
+ public string GetCustomHeaderValue(string headerKey)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return null;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return null;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return null;
+ return webView.Call("GetCustomHeaderValue", headerKey);
+#endif
+ }
+
+ public void RemoveCustomHeader(string headerKey)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("RemoveCustomHeader", headerKey);
+#endif
+ }
+
+ public void ClearCustomHeader()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("ClearCustomHeader");
+#endif
+ }
+
+ public void ClearCookie(string url, string name)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookie", url, name);
+#endif
+ }
+
+ public void ClearCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookies");
+#endif
+ }
+
+
+ public void SaveCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SaveCookies");
+#endif
+ }
+
+
+ public void GetCookies(string url)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("GetCookies", url);
+#else
+ //TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetBasicAuthInfo(string userName, string password)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 basic auth not implemented in native plugin
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetBasicAuthInfo(webView, userName, password);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetBasicAuthInfo", userName, password);
+#endif
+ }
+
+ public void ClearCache(bool includeDiskFiles)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 cache clear not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCache(webView, includeDiskFiles);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCache", includeDiskFiles);
+#endif
+ }
+
+
+ public void SetTextZoom(int textZoom)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 text zoom not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ //TODO: UNSUPPORTED
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SetTextZoom", textZoom);
+#endif
+ }
+
+ public void SetMixedContentMode(int mode) // 0: MIXED_CONTENT_ALWAYS_ALLOW, 1: MIXED_CONTENT_NEVER_ALLOW, 2: MIXED_CONTENT_COMPATIBILITY_MODE
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 mixed content mode not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ //TODO: UNSUPPORTED
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SetMixedContentMode", mode);
+#endif
+ }
+
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
+ }
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null) {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus) {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;) {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap) {
+ var w = _CWebViewPlugin_BitmapWidth(webView);
+ var h = _CWebViewPlugin_BitmapHeight(webView);
+ var f = (_CWebViewPlugin_BitmapARGB(webView)) ? TextureFormat.ARGB32 : TextureFormat.RGBA32;
+ if (w > 0 && h > 0) {
+ if (texture == null || texture.width != w || texture.height != h) {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, f, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+#if UNITY_2018_2_OR_NEWER
+#else
+ textureDataBuffer = new byte[w * h * 4];
+#endif
+ }
+ if (texture != null) {
+#if UNITY_2018_2_OR_NEWER
+ var ptr = _CWebViewPlugin_Render(webView, IntPtr.Zero);
+ if (ptr != IntPtr.Zero) {
+ texture.LoadRawTextureData(ptr, w * h * 4);
+ texture.Apply();
+ }
+#else
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+#endif
+ }
+ }
+ }
+ }
+
+ void UpdateBGTransform()
+ {
+ if (bg != null) {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ public int bitmapRefreshCycle = 1;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type) {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
+ break;
+ }
+ switch (Event.current.type) {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
+ break;
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString)) {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null) {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix
+ = Matrix4x4.TRS(
+ new Vector3(0, Screen.height, 0),
+ Quaternion.identity,
+ new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
+ break;
+ }
+ }
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
+ }
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null)
+ {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus)
+ {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;)
+ {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i))
+ {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap)
+ {
+ var w = _CWebViewPlugin_BitmapWidth(webView);
+ var h = _CWebViewPlugin_BitmapHeight(webView);
+ if (w > 0 && h > 0)
+ {
+ if (texture == null || texture.width != w || texture.height != h)
+ {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, TextureFormat.RGBA32, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+ textureDataBuffer = new byte[w * h * 4];
+ }
+ if (textureDataBuffer != null && textureDataBuffer.Length > 0)
+ {
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+ }
+ }
+ }
+ }
+
+ void UpdateBGTransform()
+ {
+ if (bg != null)
+ {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ // On Windows, CapturePreview is heavy; default 10 = refresh every 10th frame for better FPS.
+ public int bitmapRefreshCycle = 10;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type)
+ {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
+ break;
+ }
+ switch (Event.current.type)
+ {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
+ break;
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString))
+ {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null)
+ {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix = Matrix4x4.TRS(new Vector3(0, Screen.height, 0), Quaternion.identity, new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
+ break;
+ }
+ }
+#endif
+ }
+}
diff --git a/dist/package/Assets/Plugins/WebViewObject.cs.meta b/dist/package/Assets/Plugins/WebViewObject.cs.meta
new file mode 100644
index 00000000..32948634
--- /dev/null
+++ b/dist/package/Assets/Plugins/WebViewObject.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d4d2b188f50df4b299eb714ef4360ee9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows.meta b/dist/package/Assets/Plugins/Windows.meta
new file mode 100644
index 00000000..4c07dfab
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9a69d5fe42677e84b8100f858cb66df7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x64.meta b/dist/package/Assets/Plugins/Windows/x64.meta
new file mode 100644
index 00000000..6d0d8b42
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x64.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0763797fead754049b4255cee0993cb7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x64/README.txt b/dist/package/Assets/Plugins/Windows/x64/README.txt
new file mode 100644
index 00000000..51c69ca2
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x64/README.txt
@@ -0,0 +1,4 @@
+Place the Windows WebView plugin DLL here:
+ WebView.dll (64-bit, built from plugins/Windows)
+
+Build instructions: see plugins/Windows/README.md
diff --git a/dist/package/Assets/Plugins/Windows/x64/README.txt.meta b/dist/package/Assets/Plugins/Windows/x64/README.txt.meta
new file mode 100644
index 00000000..f1b77afa
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x64/README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f62969be983297d4aa100a72a1ef672d
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x64/WebView.dll b/dist/package/Assets/Plugins/Windows/x64/WebView.dll
new file mode 100755
index 00000000..34243f2e
Binary files /dev/null and b/dist/package/Assets/Plugins/Windows/x64/WebView.dll differ
diff --git a/dist/package/Assets/Plugins/Windows/x64/WebView.dll.meta b/dist/package/Assets/Plugins/Windows/x64/WebView.dll.meta
new file mode 100644
index 00000000..a9dd066f
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x64/WebView.dll.meta
@@ -0,0 +1,70 @@
+fileFormatVersion: 2
+guid: 8c2ac249de280af4ca5514719b5a934a
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ :
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 0
+ Exclude Linux64: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 1
+ Exclude Win64: 0
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ DefaultValueInitialized: true
+ OS: Windows
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ - first:
+ Standalone: Win
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x86.meta b/dist/package/Assets/Plugins/Windows/x86.meta
new file mode 100644
index 00000000..0dab753e
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x86.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 796e71d62d97fe14fa9d58010d2976fb
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x86/README.txt b/dist/package/Assets/Plugins/Windows/x86/README.txt
new file mode 100644
index 00000000..ceabd77e
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x86/README.txt
@@ -0,0 +1,4 @@
+Place the Windows WebView plugin DLL here:
+ WebView.dll (32-bit, built from plugins/Windows)
+
+Build instructions: see plugins/Windows/README.md
diff --git a/dist/package/Assets/Plugins/Windows/x86/README.txt.meta b/dist/package/Assets/Plugins/Windows/x86/README.txt.meta
new file mode 100644
index 00000000..e8efe961
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x86/README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ea032e1e67ffb2845b15ed2d0ca48d7a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/Windows/x86/WebView.dll b/dist/package/Assets/Plugins/Windows/x86/WebView.dll
new file mode 100755
index 00000000..908e4d9c
Binary files /dev/null and b/dist/package/Assets/Plugins/Windows/x86/WebView.dll differ
diff --git a/dist/package/Assets/Plugins/Windows/x86/WebView.dll.meta b/dist/package/Assets/Plugins/Windows/x86/WebView.dll.meta
new file mode 100644
index 00000000..7ce685e3
--- /dev/null
+++ b/dist/package/Assets/Plugins/Windows/x86/WebView.dll.meta
@@ -0,0 +1,70 @@
+fileFormatVersion: 2
+guid: ed66942567a0baa4e99daba51749d1c5
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ :
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 0
+ Exclude Linux64: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 0
+ Exclude Win64: 1
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: x86
+ DefaultValueInitialized: true
+ OS: Windows
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: x86_64
+ - first:
+ Standalone: Win
+ second:
+ enabled: 1
+ settings:
+ CPU: x86
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/iOS.meta b/dist/package/Assets/Plugins/iOS.meta
new file mode 100644
index 00000000..352898e8
--- /dev/null
+++ b/dist/package/Assets/Plugins/iOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a53a54acdc5d64291aa49766bb494025
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/iOS/WebView.mm b/dist/package/Assets/Plugins/iOS/WebView.mm
new file mode 100644
index 00000000..01d6dfc4
--- /dev/null
+++ b/dist/package/Assets/Plugins/iOS/WebView.mm
@@ -0,0 +1,1309 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#if !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
+
+#import
+#import
+
+// NOTE: we need extern without "C" before unity 4.5
+//extern UIViewController *UnityGetGLViewController();
+extern "C" UIViewController *UnityGetGLViewController();
+extern "C" void UnitySendMessage(const char *, const char *, const char *);
+
+// cf. https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak/33365424#33365424
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
+
+@protocol WebViewProtocol
+@property (nonatomic, getter=isOpaque) BOOL opaque;
+@property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
+@property (nonatomic, getter=isHidden) BOOL hidden;
+@property (nonatomic) CGRect frame;
+@property (nullable, nonatomic, weak) id navigationDelegate;
+@property (nullable, nonatomic, weak) id UIDelegate;
+@property (nullable, nonatomic, readonly, copy) NSURL *URL;
+- (void)load:(NSURLRequest *)request;
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
+@property (nonatomic, readonly) BOOL canGoBack;
+@property (nonatomic, readonly) BOOL canGoForward;
+- (void)goBack;
+- (void)goForward;
+- (void)reload;
+- (void)stopLoading;
+- (void)setScrollbarsVisibility:(BOOL)visibility;
+- (void)setScrollBounce:(BOOL)enable;
+@end
+
+@interface WKWebView(WebViewProtocolConformed)
+@end
+
+@implementation WKWebView(WebViewProtocolConformed)
+
+- (void)load:(NSURLRequest *)request
+{
+ WKWebView *webView = (WKWebView *)self;
+ NSURL *url = [request URL];
+ if ([url.absoluteString hasPrefix:@"file:"]) {
+ NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
+ [webView loadFileURL:url allowingReadAccessToURL:top];
+ } else {
+ [webView loadRequest:request];
+ }
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [headerDictionary allKeys]) {
+ [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ WKWebView *webView = (WKWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface CWebViewPlugin : NSObject
+{
+ UIView *webView;
+ NSString *gameObjectName;
+ NSMutableDictionary *customRequestHeader;
+ BOOL googleAppRedirectionEnabled;
+ BOOL alertDialogEnabled;
+ NSRegularExpression *allowRegex;
+ NSRegularExpression *denyRegex;
+ NSRegularExpression *hookRegex;
+ NSString *basicAuthUserName;
+ NSString *basicAuthPassword;
+}
+@end
+
+@implementation CWebViewPlugin
+
+static WKProcessPool *_sharedProcessPool;
+static NSMutableArray *_instances = [[NSMutableArray alloc] init];
+
+- (BOOL)isInitialized
+{
+ return webView != nil;
+}
+
+- (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode allowsLinkPreview:(BOOL)allowsLinkPreview allowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures radius:(int)radius
+{
+ self = [super init];
+
+ gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
+ customRequestHeader = [[NSMutableDictionary alloc] init];
+ googleAppRedirectionEnabled = false;
+ alertDialogEnabled = true;
+ allowRegex = nil;
+ denyRegex = nil;
+ hookRegex = nil;
+ basicAuthUserName = nil;
+ basicAuthPassword = nil;
+ UIView *view = UnityGetGLViewController().view;
+ if (enableWKWebView && [WKWebView class]) {
+ if (_sharedProcessPool == NULL) {
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ }
+ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *controller = [[WKUserContentController alloc] init];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"unityControl"];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"saveDataURL"];
+ {
+ NSString *str = @"\
+window.Unity = { \
+ call: function(msg) { \
+ window.webkit.messageHandlers.unityControl.postMessage(msg); \
+ }, \
+ saveDataURL: function(fileName, dataURL) { \
+ window.webkit.messageHandlers.saveDataURL.postMessage(fileName + '\t' + dataURL); \
+ } \
+}; \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ if (!zoom) {
+ NSString *str = @"\
+(function() { \
+ var meta = document.querySelector('meta[name=viewport]'); \
+ if (meta == null) { \
+ meta = document.createElement('meta'); \
+ meta.name = 'viewport'; \
+ } \
+ meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
+ var head = document.getElementsByTagName('head')[0]; \
+ head.appendChild(meta); \
+})(); \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ configuration.userContentController = controller;
+ configuration.allowsInlineMediaPlayback = true;
+ if (@available(iOS 10.0, *)) {
+ configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+ } else {
+ if (@available(iOS 9.0, *)) {
+ configuration.requiresUserActionForMediaPlayback = NO;
+ } else {
+ configuration.mediaPlaybackRequiresUserAction = NO;
+ }
+ }
+ configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+ configuration.processPool = _sharedProcessPool;
+ if (@available(iOS 13.0, *)) {
+ configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ // cf. https://stackoverflow.com/questions/35554814/wkwebview-xmlhttprequest-with-file-url/44365081#44365081
+ try {
+ [configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+ try {
+ [configuration setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+#endif
+ WKWebView *wkwebView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
+#if UNITYWEBVIEW_DEVELOPMENT
+ NSOperatingSystemVersion version = { 16, 4, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ wkwebView.inspectable = true;
+ }
+#endif
+ wkwebView.allowsLinkPreview = allowsLinkPreview;
+ wkwebView.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
+ webView = wkwebView;
+ webView.UIDelegate = self;
+ webView.navigationDelegate = self;
+ if (radius > 0) {
+ webView.layer.cornerRadius = radius;
+ webView.layer.masksToBounds = YES;
+ }
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
+ }
+ // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
+ // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
+ if (@available(iOS 11.0, *)) {
+ ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+ } else {
+ //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
+ }
+ } else {
+ webView = nil;
+ return self;
+ }
+ if (transparent) {
+ webView.opaque = NO;
+ webView.backgroundColor = [UIColor clearColor];
+ }
+ webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ webView.hidden = YES;
+
+ [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
+
+ [view addSubview:webView];
+
+ //set webview for Unity 6 accessibility hierarchy
+ NSMutableArray *accessibilityElements
+ = view.accessibilityElements ? [view.accessibilityElements mutableCopy] : [NSMutableArray array];
+ [accessibilityElements addObject:(UIAccessibilityElement *)webView];
+ view.accessibilityElements = accessibilityElements;
+
+ return self;
+}
+
+- (void)dispose
+{
+ if (webView != nil) {
+ UIView *webView0 = webView;
+ webView = nil;
+ if ([webView0 isKindOfClass:[WKWebView class]]) {
+ webView0.UIDelegate = nil;
+ webView0.navigationDelegate = nil;
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"saveDataURL"];
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"unityControl"];
+ }
+ [webView0 stopLoading];
+ [webView0 removeFromSuperview];
+ [webView0 removeObserver:self forKeyPath:@"loading"];
+
+ //remove the WebViewObject from Unity hierarchy tree
+ UIView *view = UnityGetGLViewController().view;
+ NSMutableArray *accessibilityElements
+ = view.accessibilityElements ? [view.accessibilityElements mutableCopy] : [NSMutableArray array];
+ [accessibilityElements removeObject: (UIAccessibilityElement *)webView0];
+ view.accessibilityElements = accessibilityElements;
+ }
+ basicAuthPassword = nil;
+ basicAuthUserName = nil;
+ hookRegex = nil;
+ denyRegex = nil;
+ allowRegex = nil;
+ customRequestHeader = nil;
+ gameObjectName = nil;
+}
+
++ (void)resetSharedProcessPool
+{
+ // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
+ if ([obj->webView isKindOfClass:[WKWebView class]]) {
+ WKWebView *webView = (WKWebView *)obj->webView;
+ webView.configuration.processPool = _sharedProcessPool;
+ }
+ }];
+}
+
++ (void)clearCookie:(const char *)name of:(const char *)url
+{
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ if (nsurl == nil) {
+ return;
+ }
+ NSString *nsname = [NSString stringWithUTF8String:name];
+ if (@available(iOS 9.0, *)) {
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ [array
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStore deleteCookie:cookie completionHandler:^{}];
+ }
+ }];
+ }];
+ } else {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }];
+ }
+}
+
++ (void)clearCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+
+ // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
+ NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
+ NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
+ [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
+ [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
+
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [cookieStorage deleteCookie:cookie];
+ }];
+
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ // cf. https://stackoverflow.com/questions/46465070/how-to-delete-cookies-from-wkhttpcookiestore/47928399#47928399
+ NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
+ modifiedSince:date
+ completionHandler:^{}];
+ }
+}
+
++ saveCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+}
+
+- (void)getCookies:(const char *)url
+{
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ [array enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.domain isEqualToString:nsurl.host]) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }];
+ } else {
+ [CWebViewPlugin resetSharedProcessPool];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message {
+
+ // Log out the message received
+ //NSLog(@"Received event %@", message.body);
+ if ([message.name isEqualToString:@"unityControl"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
+ } else if ([message.name isEqualToString:@"saveDataURL"]) {
+ NSRange range = [message.body rangeOfString:@"\t"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *fileName = [[message.body substringWithRange:NSMakeRange(0, range.location)] lastPathComponent];
+ NSString *dataURL = [message.body substringFromIndex:(range.location + 1)];
+ range = [dataURL rangeOfString:@"data:"];
+ if (range.location != 0) {
+ return;
+ }
+ NSString *tmp = [dataURL substringFromIndex:[@"data:" length]];
+ range = [tmp rangeOfString:@";"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *base64data = [tmp substringFromIndex:(range.location + 1 + [@"base64," length])];
+ NSString *type = [tmp substringWithRange:NSMakeRange(0, range.location)];
+ NSData *data = [[NSData alloc] initWithBase64EncodedString:base64data options:0];
+ NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ path = [path stringByAppendingString:@"/Downloads"];
+ BOOL isDir;
+ NSError *err = nil;
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
+ if (!isDir) {
+ return;
+ }
+ } else {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
+ if (err != nil) {
+ return;
+ }
+ }
+ NSString *prefix = [path stringByAppendingString:@"/"];
+ path = [prefix stringByAppendingString:fileName];
+ int count = 0;
+ while ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ count++;
+ NSString *name = [fileName stringByDeletingPathExtension];
+ NSString *ext = [fileName pathExtension];
+ if (ext.length == 0) {
+ path = [NSString stringWithFormat:@"%@%@ (%d)", prefix, name, count];
+ } else {
+ path = [NSString stringWithFormat:@"%@%@ (%d).%@", prefix, name, count, ext];
+ }
+ }
+ [data writeToFile:path atomically:YES];
+ }
+
+ /*
+ // Then pull something from the device using the message body
+ NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
+
+ // Execute some JavaScript using the result?
+ NSString *exec_template = @"set_headline(\"received: %@\");";
+ NSString *exec = [NSString stringWithFormat:exec_template, version];
+ [webView evaluateJavaScript:exec completionHandler:nil];
+ */
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (webView == nil)
+ return;
+
+ if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+
+ }
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", "webViewWebContentProcessDidTerminate");
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ if (!navigationAction.targetFrame.isMainFrame) {
+ [wkWebView loadRequest:navigationAction.request];
+ }
+ return nil;
+}
+
+- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ if (webView == nil) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ NSURL *nsurl = [navigationAction.request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
+ && ![url hasPrefix:@"about:srcdoc"] // for iframe srcdoc attribute
+ && ![url hasPrefix:@"file:"]
+ && ![url hasPrefix:@"http:"]
+ && ![url hasPrefix:@"https:"]) {
+ if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
+ && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else {
+ if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
+ // If the custom header is not attached, give it and make a request again.
+ if (![self isSetupedCustomHeader:[navigationAction request]]) {
+ //NSLog(@"navi ... %@", navigationAction);
+ [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ }
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ // cf. https://stackoverflow.com/questions/37086605/disable-wkwebview-for-opening-links-to-redirect-to-apps-installed-on-my-iphone/76948270#76948270
+ if (!googleAppRedirectionEnabled
+ && [url hasPrefix:@"https://www.google.com/"]
+ && navigationAction.navigationType == WKNavigationTypeLinkActivated) {
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode >= 400) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
+ }
+
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+// alert
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler();
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler();
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// confirm
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(NO);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ completionHandler(YES);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(NO);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// prompt
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(nil);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:prompt
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+ textField.text = defaultText;
+ }];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
+ completionHandler(input);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(nil);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
+{
+ NSURLSessionAuthChallengeDisposition disposition;
+ NSURLCredential *credential;
+ if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
+ disposition = NSURLSessionAuthChallengeUseCredential;
+ credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
+ } else {
+ disposition = NSURLSessionAuthChallengePerformDefaultHandling;
+ credential = nil;
+ }
+ completionHandler(disposition, credential);
+}
+
+- (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
+{
+ // Check for additional custom header.
+ for (NSString *key in [customRequestHeader allKeys]) {
+ if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [customRequestHeader allKeys]) {
+ [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
+{
+ if (webView == nil)
+ return;
+ UIView *view = UnityGetGLViewController().view;
+ CGRect frame = webView.frame;
+ CGRect screen = view.bounds;
+ if (relative) {
+ frame.size.width = floor(screen.size.width * (1.0f - left - right));
+ frame.size.height = floor(screen.size.height * (1.0f - top - bottom));
+ frame.origin.x = floor(screen.size.width * left);
+ frame.origin.y = floor(screen.size.height * top);
+ } else {
+ CGFloat scale = 1.0f / [self getScale:view];
+ frame.size.width = floor(screen.size.width - scale * (left + right));
+ frame.size.height = floor(screen.size.height - scale * (top + bottom));
+ frame.origin.x = floor(scale * left);
+ frame.origin.y = floor(scale * top);
+ }
+ webView.frame = frame;
+}
+
+- (CGFloat)getScale:(UIView *)view
+{
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ return view.window.screen.nativeScale;
+ return view.contentScaleFactor;
+}
+
+- (void)setVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ webView.hidden = visibility ? NO : YES;
+}
+
+- (void)setInteractionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ webView.userInteractionEnabled = enabled;
+}
+
+- (void)setGoogleAppRedirectionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ googleAppRedirectionEnabled = enabled;
+}
+
+- (void)setAlertDialogEnabled:(BOOL)enabled
+{
+ alertDialogEnabled = enabled;
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ [webView setScrollbarsVisibility:visibility];
+}
+
+- (void)setScrollBounceEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ [webView setScrollBounce:enabled];
+}
+
+- (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
+{
+ NSError *err = nil;
+ NSRegularExpression *allow = nil;
+ NSRegularExpression *deny = nil;
+ NSRegularExpression *hook = nil;
+ if (allowPattern == nil || *allowPattern == '\0') {
+ allow = nil;
+ } else {
+ allow
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (denyPattern == nil || *denyPattern == '\0') {
+ deny = nil;
+ } else {
+ deny
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (hookPattern == nil || *hookPattern == '\0') {
+ hook = nil;
+ } else {
+ hook
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ allowRegex = allow;
+ denyRegex = deny;
+ hookRegex = hook;
+ return YES;
+}
+
+- (void)loadURL:(const char *)url
+{
+ if (webView == nil)
+ return;
+ NSString *urlStr = [NSString stringWithUTF8String:url];
+ NSURL *nsurl = [NSURL URLWithString:urlStr];
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
+ [webView load:request];
+}
+
+- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
+{
+ if (webView == nil)
+ return;
+ NSString *htmlStr = [NSString stringWithUTF8String:html];
+ NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
+ NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
+ [webView loadHTML:htmlStr baseURL:baseNSUrl];
+}
+
+- (void)evaluateJS:(const char *)js
+{
+ if (webView == nil)
+ return;
+ NSString *jsStr = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
+}
+
+- (int)progress
+{
+ if (webView == nil)
+ return 0;
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ return (int)([(WKWebView *)webView estimatedProgress] * 100);
+ } else {
+ return 0;
+ }
+}
+
+- (BOOL)canGoBack
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoForward];
+}
+
+- (void)goBack
+{
+ if (webView == nil)
+ return;
+ [webView goBack];
+}
+
+- (void)goForward
+{
+ if (webView == nil)
+ return;
+ [webView goForward];
+}
+
+- (void)reload
+{
+ if (webView == nil)
+ return;
+ [webView reload];
+}
+
+- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *valueString = [NSString stringWithUTF8String:headerValue];
+
+ [customRequestHeader setObject:valueString forKey:keyString];
+}
+
+- (void)removeCustomRequestHeader:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+
+ if ([[customRequestHeader allKeys]containsObject:keyString]) {
+ [customRequestHeader removeObjectForKey:keyString];
+ }
+}
+
+- (void)clearCustomRequestHeader
+{
+ [customRequestHeader removeAllObjects];
+}
+
+- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *result = [customRequestHeader objectForKey:keyString];
+ if (!result) {
+ return NULL;
+ }
+
+ const char *s = [result UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+- (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
+{
+ basicAuthUserName = [NSString stringWithUTF8String:userName];
+ basicAuthPassword = [NSString stringWithUTF8String:password];
+}
+
+- (void)clearCache:(BOOL)includeDiskFiles
+{
+ if (webView == nil)
+ return;
+ NSMutableSet *types = [NSMutableSet setWithArray:@[WKWebsiteDataTypeMemoryCache]];
+ if (includeDiskFiles) {
+ [types addObject:WKWebsiteDataTypeDiskCache];
+ }
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:types modifiedSince:date completionHandler:^{}];
+}
+
+- (void)setAllMediaPlaybackSuspended:(BOOL)suspended
+{
+ NSOperatingSystemVersion version = { 15, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ [(WKWebView *)webView setAllMediaPlaybackSuspended:suspended completionHandler:nil];
+ }
+ }
+}
+@end
+
+extern "C" {
+ BOOL _CWebViewPlugin_IsInitialized(void *instance);
+ void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius);
+ void _CWebViewPlugin_Destroy(void *instance);
+ void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative);
+ void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
+ BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
+ void _CWebViewPlugin_LoadURL(void *instance, const char *url);
+ void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
+ void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
+ int _CWebViewPlugin_Progress(void *instance);
+ BOOL _CWebViewPlugin_CanGoBack(void *instance);
+ BOOL _CWebViewPlugin_CanGoForward(void *instance);
+ void _CWebViewPlugin_GoBack(void *instance);
+ void _CWebViewPlugin_GoForward(void *instance);
+ void _CWebViewPlugin_Reload(void *instance);
+ void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
+ void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCustomHeader(void *instance);
+ void _CWebViewPlugin_ClearCookie(const char *url, const char *name);
+ void _CWebViewPlugin_ClearCookies();
+ void _CWebViewPlugin_SaveCookies();
+ void _CWebViewPlugin_GetCookies(void *instance, const char *url);
+ const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
+ void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
+ void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
+ void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended);
+}
+
+BOOL _CWebViewPlugin_IsInitialized(void *instance)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin isInitialized];
+}
+
+void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius)
+{
+ if (! (enableWKWebView && [WKWebView class]))
+ return nil;
+ WKContentMode wkContentMode = WKContentModeRecommended;
+ switch (contentMode) {
+ case 1:
+ wkContentMode = WKContentModeMobile;
+ break;
+ case 2:
+ wkContentMode = WKContentModeDesktop;
+ break;
+ default:
+ wkContentMode = WKContentModeRecommended;
+ break;
+ }
+ CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode allowsLinkPreview:allowsLinkPreview allowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures radius:radius];
+ [_instances addObject:webViewPlugin];
+ return (__bridge_retained void *)webViewPlugin;
+}
+
+void _CWebViewPlugin_Destroy(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
+ [_instances removeObject:webViewPlugin];
+ [webViewPlugin dispose];
+ webViewPlugin = nil;
+}
+
+void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
+}
+
+void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setInteractionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setGoogleAppRedirectionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAlertDialogEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollbarsVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollBounceEnabled:enabled];
+}
+
+BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
+}
+
+void _CWebViewPlugin_LoadURL(void *instance, const char *url)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadURL:url];
+}
+
+void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadHTML:html baseURL:baseUrl];
+}
+
+void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin evaluateJS:js];
+}
+
+int _CWebViewPlugin_Progress(void *instance)
+{
+ if (instance == NULL)
+ return 0;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin progress];
+}
+
+BOOL _CWebViewPlugin_CanGoBack(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoBack];
+}
+
+BOOL _CWebViewPlugin_CanGoForward(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoForward];
+}
+
+void _CWebViewPlugin_GoBack(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goBack];
+}
+
+void _CWebViewPlugin_GoForward(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goForward];
+}
+
+void _CWebViewPlugin_Reload(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin reload];
+}
+
+void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
+}
+
+void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin removeCustomRequestHeader:headerKey];
+}
+
+void _CWebViewPlugin_ClearCustomHeader(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCustomRequestHeader];
+}
+
+void _CWebViewPlugin_ClearCookie(const char *url, const char *name)
+{
+ [CWebViewPlugin clearCookie:name of:url];
+}
+
+void _CWebViewPlugin_ClearCookies()
+{
+ [CWebViewPlugin clearCookies];
+}
+
+void _CWebViewPlugin_SaveCookies()
+{
+ [CWebViewPlugin saveCookies];
+}
+
+void _CWebViewPlugin_GetCookies(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin getCookies:url];
+}
+
+const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return NULL;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin getCustomRequestHeaderValue:headerKey];
+}
+
+void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setBasicAuthInfo:userName password:password];
+}
+
+void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCache:includeDiskFiles];
+}
+
+void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAllMediaPlaybackSuspended:suspended];
+}
+#endif // !(__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
diff --git a/dist/package/Assets/Plugins/iOS/WebView.mm.meta b/dist/package/Assets/Plugins/iOS/WebView.mm.meta
new file mode 100644
index 00000000..fb91d132
--- /dev/null
+++ b/dist/package/Assets/Plugins/iOS/WebView.mm.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 2cabb4f60971742a28f3bd04e65de504
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm b/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm
new file mode 100644
index 00000000..7909a36f
--- /dev/null
+++ b/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm
@@ -0,0 +1,1385 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+
+#import
+#import
+
+// NOTE: we need extern without "C" before unity 4.5
+//extern UIViewController *UnityGetGLViewController();
+extern "C" UIViewController *UnityGetGLViewController();
+extern "C" void UnitySendMessage(const char *, const char *, const char *);
+
+// cf. https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak/33365424#33365424
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
+
+@protocol WebViewProtocol
+@property (nonatomic, getter=isOpaque) BOOL opaque;
+@property (nullable, nonatomic, copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
+@property (nonatomic, getter=isHidden) BOOL hidden;
+@property (nonatomic, getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
+@property (nonatomic) CGRect frame;
+@property (nonatomic, readonly, strong) UIScrollView *scrollView;
+@property (nullable, nonatomic, assign) id delegate;
+@property (nullable, nonatomic, weak) id navigationDelegate;
+@property (nullable, nonatomic, weak) id UIDelegate;
+@property (nullable, nonatomic, readonly, copy) NSURL *URL;
+- (void)load:(NSURLRequest *)request;
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl;
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
+@property (nonatomic, readonly) BOOL canGoBack;
+@property (nonatomic, readonly) BOOL canGoForward;
+- (void)goBack;
+- (void)goForward;
+- (void)reload;
+- (void)stopLoading;
+- (void)setScrollbarsVisibility:(BOOL)visibility;
+- (void)setScrollBounce:(BOOL)enable;
+@end
+
+@interface WKWebView(WebViewProtocolConformed)
+@end
+
+@implementation WKWebView(WebViewProtocolConformed)
+
+@dynamic delegate;
+
+- (void)load:(NSURLRequest *)request
+{
+ WKWebView *webView = (WKWebView *)self;
+ NSURL *url = [request URL];
+ if ([url.absoluteString hasPrefix:@"file:"]) {
+ NSURL *top = [NSURL URLWithString:[[url absoluteString] stringByDeletingLastPathComponent]];
+ [webView loadFileURL:url allowingReadAccessToURL:top];
+ } else {
+ [webView loadRequest:request];
+ }
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest with:(NSDictionary *)headerDictionary
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [headerDictionary allKeys]) {
+ [convertedRequest setValue:headerDictionary[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ WKWebView *webView = (WKWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ WKWebView *webView = (WKWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface UIWebView(WebViewProtocolConformed)
+@end
+
+@implementation UIWebView(WebViewProtocolConformed)
+
+@dynamic navigationDelegate;
+@dynamic UIDelegate;
+
+- (NSURL *)URL
+{
+ return [NSURL URLWithString:[self stringByEvaluatingJavaScriptFromString:@"document.URL"]];
+}
+
+- (void)load:(NSURLRequest *)request
+{
+ UIWebView *webView = (UIWebView *)self;
+ [webView loadRequest:request];
+}
+
+- (void)loadHTML:(NSString *)html baseURL:(NSURL *)baseUrl
+{
+ UIWebView *webView = (UIWebView *)self;
+ [webView loadHTMLString:html baseURL:baseUrl];
+}
+
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler
+{
+ NSString *result = [self stringByEvaluatingJavaScriptFromString:javaScriptString];
+ if (completionHandler) {
+ completionHandler(result, nil);
+ }
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ UIWebView *webView = (UIWebView *)self;
+ webView.scrollView.showsHorizontalScrollIndicator = visibility;
+ webView.scrollView.showsVerticalScrollIndicator = visibility;
+}
+
+- (void)setScrollBounce:(BOOL)enable
+{
+ UIWebView *webView = (UIWebView *)self;
+ webView.scrollView.bounces = enable;
+}
+
+@end
+
+@interface CWebViewPlugin : NSObject
+{
+ UIView *webView;
+ NSString *gameObjectName;
+ NSMutableDictionary *customRequestHeader;
+ BOOL googleAppRedirectionEnabled;
+ BOOL alertDialogEnabled;
+ NSRegularExpression *allowRegex;
+ NSRegularExpression *denyRegex;
+ NSRegularExpression *hookRegex;
+ NSString *basicAuthUserName;
+ NSString *basicAuthPassword;
+}
+@end
+
+@implementation CWebViewPlugin
+
+static WKProcessPool *_sharedProcessPool;
+static NSMutableArray *_instances = [[NSMutableArray alloc] init];
+
+- (BOOL)isInitialized
+{
+ return webView != nil;
+}
+
+- (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent zoom:(BOOL)zoom ua:(const char *)ua enableWKWebView:(BOOL)enableWKWebView contentMode:(WKContentMode)contentMode allowsLinkPreview:(BOOL)allowsLinkPreview allowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures radius:(int)radius
+{
+ self = [super init];
+
+ gameObjectName = [NSString stringWithUTF8String:gameObjectName_];
+ customRequestHeader = [[NSMutableDictionary alloc] init];
+ googleAppRedirectionEnabled = false;
+ alertDialogEnabled = true;
+ allowRegex = nil;
+ denyRegex = nil;
+ hookRegex = nil;
+ basicAuthUserName = nil;
+ basicAuthPassword = nil;
+ UIView *view = UnityGetGLViewController().view;
+ if (enableWKWebView && [WKWebView class]) {
+ if (_sharedProcessPool == NULL) {
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ }
+ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *controller = [[WKUserContentController alloc] init];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"unityControl"];
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"saveDataURL"];
+ {
+ NSString *str = @"\
+window.Unity = { \
+ call: function(msg) { \
+ window.webkit.messageHandlers.unityControl.postMessage(msg); \
+ }, \
+ saveDataURL: function(fileName, dataURL) { \
+ window.webkit.messageHandlers.saveDataURL.postMessage(fileName + '\t' + dataURL); \
+ } \
+}; \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ if (!zoom) {
+ NSString *str = @"\
+(function() { \
+ var meta = document.querySelector('meta[name=viewport]'); \
+ if (meta == null) { \
+ meta = document.createElement('meta'); \
+ meta.name = 'viewport'; \
+ } \
+ meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
+ var head = document.getElementsByTagName('head')[0]; \
+ head.appendChild(meta); \
+})(); \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ configuration.userContentController = controller;
+ configuration.allowsInlineMediaPlayback = true;
+ if (@available(iOS 10.0, *)) {
+ configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+ } else {
+ if (@available(iOS 9.0, *)) {
+ configuration.requiresUserActionForMediaPlayback = NO;
+ } else {
+ configuration.mediaPlaybackRequiresUserAction = NO;
+ }
+ }
+ configuration.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+ configuration.processPool = _sharedProcessPool;
+ if (@available(iOS 13.0, *)) {
+ configuration.defaultWebpagePreferences.preferredContentMode = contentMode;
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ // cf. https://stackoverflow.com/questions/35554814/wkwebview-xmlhttprequest-with-file-url/44365081#44365081
+ try {
+ [configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+ try {
+ [configuration setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"];
+ }
+ catch (NSException *ex) {
+ }
+#endif
+ WKWebView *wkwebView = [[WKWebView alloc] initWithFrame:view.frame configuration:configuration];
+#if UNITYWEBVIEW_DEVELOPMENT
+ NSOperatingSystemVersion version = { 16, 4, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ wkwebView.inspectable = true;
+ }
+#endif
+ wkwebView.allowsLinkPreview = allowsLinkPreview;
+ wkwebView.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
+ webView = wkwebView;
+ webView.UIDelegate = self;
+ webView.navigationDelegate = self;
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ ((WKWebView *)webView).customUserAgent = [[NSString alloc] initWithUTF8String:ua];
+ }
+ // cf. https://rick38yip.medium.com/wkwebview-weird-spacing-issue-in-ios-13-54a4fc686f72
+ // cf. https://stackoverflow.com/questions/44390971/automaticallyadjustsscrollviewinsets-was-deprecated-in-ios-11-0
+ if (@available(iOS 11.0, *)) {
+ ((WKWebView *)webView).scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+ } else {
+ //UnityGetGLViewController().automaticallyAdjustsScrollViewInsets = false;
+ }
+ } else {
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ [[NSUserDefaults standardUserDefaults]
+ registerDefaults:@{ @"UserAgent": [[NSString alloc] initWithUTF8String:ua] }];
+ }
+ UIWebView *uiwebview = [[UIWebView alloc] initWithFrame:view.frame];
+ uiwebview.allowsInlineMediaPlayback = YES;
+ uiwebview.mediaPlaybackRequiresUserAction = NO;
+ webView = uiwebview;
+ webView.delegate = self;
+ }
+ if (transparent) {
+ webView.opaque = NO;
+ webView.backgroundColor = [UIColor clearColor];
+ }
+ if (radius > 0) {
+ webView.layer.cornerRadius = radius;
+ webView.layer.masksToBounds = YES;
+ }
+ webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ webView.hidden = YES;
+
+ [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
+
+ [view addSubview:webView];
+
+ return self;
+}
+
+- (void)dispose
+{
+ if (webView != nil) {
+ UIView *webView0 = webView;
+ webView = nil;
+ if ([webView0 isKindOfClass:[WKWebView class]]) {
+ webView0.UIDelegate = nil;
+ webView0.navigationDelegate = nil;
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"saveDataURL"];
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"unityControl"];
+ } else {
+ webView0.delegate = nil;
+ }
+ [webView0 stopLoading];
+ [webView0 removeFromSuperview];
+ [webView0 removeObserver:self forKeyPath:@"loading"];
+ }
+ basicAuthPassword = nil;
+ basicAuthUserName = nil;
+ hookRegex = nil;
+ denyRegex = nil;
+ allowRegex = nil;
+ customRequestHeader = nil;
+ gameObjectName = nil;
+}
+
++ (void)resetSharedProcessPool
+{
+ // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
+ if ([obj->webView isKindOfClass:[WKWebView class]]) {
+ WKWebView *webView = (WKWebView *)obj->webView;
+ webView.configuration.processPool = _sharedProcessPool;
+ }
+ }];
+}
+
++ (void)clearCookie:(const char *)name of:(const char *)url
+{
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ if (nsurl == nil) {
+ return;
+ }
+ NSString *nsname = [NSString stringWithUTF8String:name];
+ if (@available(iOS 9.0, *)) {
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ [array
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStore deleteCookie:cookie completionHandler:^{}];
+ }
+ }];
+ }];
+ } else {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }];
+ }
+}
+
++ (void)clearCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+
+ // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
+ NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
+ NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
+ [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
+ [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
+
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [cookieStorage deleteCookie:cookie];
+ }];
+
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ // cf. https://stackoverflow.com/questions/46465070/how-to-delete-cookies-from-wkhttpcookiestore/47928399#47928399
+ NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
+ modifiedSince:date
+ completionHandler:^{}];
+ }
+}
+
++ saveCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+}
+
+- (void)getCookies:(const char *)url
+{
+ NSOperatingSystemVersion version = { 9, 0, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ [array enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.domain isEqualToString:nsurl.host]) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }];
+ } else {
+ [CWebViewPlugin resetSharedProcessPool];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }];
+ UnitySendMessage([gameObjectName UTF8String], "CallOnCookies", [result UTF8String]);
+ }
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message {
+
+ // Log out the message received
+ //NSLog(@"Received event %@", message.body);
+ if ([message.name isEqualToString:@"unityControl"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[NSString stringWithFormat:@"%@", message.body] UTF8String]);
+ } else if ([message.name isEqualToString:@"saveDataURL"]) {
+ NSRange range = [message.body rangeOfString:@"\t"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *fileName = [[message.body substringWithRange:NSMakeRange(0, range.location)] lastPathComponent];
+ NSString *dataURL = [message.body substringFromIndex:(range.location + 1)];
+ range = [dataURL rangeOfString:@"data:"];
+ if (range.location != 0) {
+ return;
+ }
+ NSString *tmp = [dataURL substringFromIndex:[@"data:" length]];
+ range = [tmp rangeOfString:@";"];
+ if (range.location == NSNotFound) {
+ return;
+ }
+ NSString *base64data = [tmp substringFromIndex:(range.location + 1 + [@"base64," length])];
+ NSString *type = [tmp substringWithRange:NSMakeRange(0, range.location)];
+ NSData *data = [[NSData alloc] initWithBase64EncodedString:base64data options:0];
+ NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ path = [path stringByAppendingString:@"/Downloads"];
+ BOOL isDir;
+ NSError *err = nil;
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
+ if (!isDir) {
+ return;
+ }
+ } else {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
+ if (err != nil) {
+ return;
+ }
+ }
+ NSString *prefix = [path stringByAppendingString:@"/"];
+ path = [prefix stringByAppendingString:fileName];
+ int count = 0;
+ while ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ count++;
+ NSString *name = [fileName stringByDeletingPathExtension];
+ NSString *ext = [fileName pathExtension];
+ if (ext.length == 0) {
+ path = [NSString stringWithFormat:@"%@%@ (%d)", prefix, name, count];
+ } else {
+ path = [NSString stringWithFormat:@"%@%@ (%d).%@", prefix, name, count, ext];
+ }
+ }
+ [data writeToFile:path atomically:YES];
+ }
+
+ /*
+ // Then pull something from the device using the message body
+ NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
+
+ // Execute some JavaScript using the result?
+ NSString *exec_template = @"set_headline(\"received: %@\");";
+ NSString *exec = [NSString stringWithFormat:exec_template, version];
+ [webView evaluateJavaScript:exec completionHandler:nil];
+ */
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (webView == nil)
+ return;
+
+ if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+
+ }
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", "webViewWebContentProcessDidTerminate");
+}
+
+- (void)webView:(UIWebView *)uiWebView didFailLoadWithError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ UnitySendMessage([gameObjectName UTF8String], "CallOnError", [[error description] UTF8String]);
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)uiWebView {
+ if (webView == nil)
+ return;
+ // cf. http://stackoverflow.com/questions/10996028/uiwebview-when-did-a-page-really-finish-loading/15916853#15916853
+ if ([[uiWebView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]
+ && [webView URL] != nil) {
+ UnitySendMessage(
+ [gameObjectName UTF8String],
+ "CallOnLoaded",
+ [[[webView URL] absoluteString] UTF8String]);
+ }
+}
+
+- (BOOL)webView:(UIWebView *)uiWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
+{
+ if (webView == nil)
+ return YES;
+
+ NSURL *nsurl = [request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ return NO;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ return NO;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ return NO;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ return NO;
+ } else {
+ if (![self isSetupedCustomHeader:request]) {
+ [uiWebView loadRequest:[self constructionCustomHeader:request]];
+ return NO;
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ return YES;
+ }
+}
+
+- (WKWebView *)webView:(WKWebView *)wkWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ if (!navigationAction.targetFrame.isMainFrame) {
+ [wkWebView loadRequest:navigationAction.request];
+ }
+ return nil;
+}
+
+- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ if (webView == nil) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ NSURL *nsurl = [navigationAction.request URL];
+ NSString *url = [nsurl absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if ([url hasPrefix:@"unity:"]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallFromJS", [[url substringFromIndex:6] UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHooked", [url UTF8String]);
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (![url hasPrefix:@"about:blank"] // for loadHTML(), cf. #365
+ && ![url hasPrefix:@"about:srcdoc"] // for iframe srcdoc attribute
+ && ![url hasPrefix:@"file:"]
+ && ![url hasPrefix:@"http:"]
+ && ![url hasPrefix:@"https:"]) {
+ if([[UIApplication sharedApplication] canOpenURL:nsurl]) {
+ if (@available(iOS 10.0, *)) {
+ [[UIApplication sharedApplication] openURL:nsurl options:@{} completionHandler:nil];
+ } else {
+ [[UIApplication sharedApplication] openURL:nsurl];
+ }
+ }
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
+ && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ } else {
+ if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
+ // If the custom header is not attached, give it and make a request again.
+ if (![self isSetupedCustomHeader:[navigationAction request]]) {
+ //NSLog(@"navi ... %@", navigationAction);
+ [wkWebView loadRequest:[self constructionCustomHeader:navigationAction.request]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ }
+ }
+ UnitySendMessage([gameObjectName UTF8String], "CallOnStarted", [url UTF8String]);
+ // cf. https://stackoverflow.com/questions/37086605/disable-wkwebview-for-opening-links-to-redirect-to-apps-installed-on-my-iphone/76948270#76948270
+ if (!googleAppRedirectionEnabled
+ && [url hasPrefix:@"https://www.google.com/"]
+ && navigationAction.navigationType == WKNavigationTypeLinkActivated) {
+ [webView load:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode >= 400) {
+ UnitySendMessage([gameObjectName UTF8String], "CallOnHttpError", [[NSString stringWithFormat:@"%d", response.statusCode] UTF8String]);
+ }
+
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+// alert
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler();
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction: [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler();
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// confirm
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(NO);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ completionHandler(YES);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(NO);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+// prompt
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler
+{
+ if (!alertDialogEnabled) {
+ completionHandler(nil);
+ return;
+ }
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
+ message:prompt
+ preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+ textField.text = defaultText;
+ }];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
+ completionHandler(input);
+ }]];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction *action) {
+ completionHandler(nil);
+ }]];
+ [UnityGetGLViewController() presentViewController:alertController animated:YES completion:^{}];
+}
+
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
+{
+ NSURLSessionAuthChallengeDisposition disposition;
+ NSURLCredential *credential;
+ if (basicAuthUserName && basicAuthPassword && [challenge previousFailureCount] == 0) {
+ disposition = NSURLSessionAuthChallengeUseCredential;
+ credential = [NSURLCredential credentialWithUser:basicAuthUserName password:basicAuthPassword persistence:NSURLCredentialPersistenceForSession];
+ } else {
+ disposition = NSURLSessionAuthChallengePerformDefaultHandling;
+ credential = nil;
+ }
+ completionHandler(disposition, credential);
+}
+
+- (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
+{
+ // Check for additional custom header.
+ for (NSString *key in [customRequestHeader allKeys]) {
+ if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [customRequestHeader allKeys]) {
+ [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
+ }
+ return (NSURLRequest *)[convertedRequest copy];
+}
+
+- (void)setMargins:(float)left top:(float)top right:(float)right bottom:(float)bottom relative:(BOOL)relative
+{
+ if (webView == nil)
+ return;
+ UIView *view = UnityGetGLViewController().view;
+ CGRect frame = webView.frame;
+ CGRect screen = view.bounds;
+ if (relative) {
+ frame.size.width = floor(screen.size.width * (1.0f - left - right));
+ frame.size.height = floor(screen.size.height * (1.0f - top - bottom));
+ frame.origin.x = floor(screen.size.width * left);
+ frame.origin.y = floor(screen.size.height * top);
+ } else {
+ CGFloat scale = 1.0f / [self getScale:view];
+ frame.size.width = floor(screen.size.width - scale * (left + right));
+ frame.size.height = floor(screen.size.height - scale * (top + bottom));
+ frame.origin.x = floor(scale * left);
+ frame.origin.y = floor(scale * top);
+ }
+ webView.frame = frame;
+}
+
+- (CGFloat)getScale:(UIView *)view
+{
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ return view.window.screen.nativeScale;
+ return view.contentScaleFactor;
+}
+
+- (void)setVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ webView.hidden = visibility ? NO : YES;
+}
+
+- (void)setInteractionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ webView.userInteractionEnabled = enabled;
+}
+
+- (void)setGoogleAppRedirectionEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ googleAppRedirectionEnabled = enabled;
+}
+
+- (void)setAlertDialogEnabled:(BOOL)enabled
+{
+ alertDialogEnabled = enabled;
+}
+
+- (void)setScrollbarsVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ [webView setScrollbarsVisibility:visibility];
+}
+
+- (void)setScrollBounceEnabled:(BOOL)enabled
+{
+ if (webView == nil)
+ return;
+ [webView setScrollBounce:enabled];
+}
+
+- (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
+{
+ NSError *err = nil;
+ NSRegularExpression *allow = nil;
+ NSRegularExpression *deny = nil;
+ NSRegularExpression *hook = nil;
+ if (allowPattern == nil || *allowPattern == '\0') {
+ allow = nil;
+ } else {
+ allow
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (denyPattern == nil || *denyPattern == '\0') {
+ deny = nil;
+ } else {
+ deny
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (hookPattern == nil || *hookPattern == '\0') {
+ hook = nil;
+ } else {
+ hook
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ allowRegex = allow;
+ denyRegex = deny;
+ hookRegex = hook;
+ return YES;
+}
+
+- (void)loadURL:(const char *)url
+{
+ if (webView == nil)
+ return;
+ NSString *urlStr = [NSString stringWithUTF8String:url];
+ NSURL *nsurl = [NSURL URLWithString:urlStr];
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
+ [webView load:request];
+}
+
+- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
+{
+ if (webView == nil)
+ return;
+ NSString *htmlStr = [NSString stringWithUTF8String:html];
+ NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
+ NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
+ [webView loadHTML:htmlStr baseURL:baseNSUrl];
+}
+
+- (void)evaluateJS:(const char *)js
+{
+ if (webView == nil)
+ return;
+ NSString *jsStr = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}];
+}
+
+- (int)progress
+{
+ if (webView == nil)
+ return 0;
+ if ([webView isKindOfClass:[WKWebView class]]) {
+ return (int)([(WKWebView *)webView estimatedProgress] * 100);
+ } else {
+ return 0;
+ }
+}
+
+- (BOOL)canGoBack
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoForward];
+}
+
+- (void)goBack
+{
+ if (webView == nil)
+ return;
+ [webView goBack];
+}
+
+- (void)goForward
+{
+ if (webView == nil)
+ return;
+ [webView goForward];
+}
+
+- (void)reload
+{
+ if (webView == nil)
+ return;
+ [webView reload];
+}
+
+- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *valueString = [NSString stringWithUTF8String:headerValue];
+
+ [customRequestHeader setObject:valueString forKey:keyString];
+}
+
+- (void)removeCustomRequestHeader:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+
+ if ([[customRequestHeader allKeys]containsObject:keyString]) {
+ [customRequestHeader removeObjectForKey:keyString];
+ }
+}
+
+- (void)clearCustomRequestHeader
+{
+ [customRequestHeader removeAllObjects];
+}
+
+- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *result = [customRequestHeader objectForKey:keyString];
+ if (!result) {
+ return NULL;
+ }
+
+ const char *s = [result UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+- (void)setBasicAuthInfo:(const char *)userName password:(const char *)password
+{
+ basicAuthUserName = [NSString stringWithUTF8String:userName];
+ basicAuthPassword = [NSString stringWithUTF8String:password];
+}
+@end
+
+extern "C" {
+ BOOL _CWebViewPlugin_IsInitialized(void *instance);
+ void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius);
+ void _CWebViewPlugin_Destroy(void *instance);
+ void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative);
+ void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled);
+ void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility);
+ void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled);
+ BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
+ void _CWebViewPlugin_LoadURL(void *instance, const char *url);
+ void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
+ void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
+ int _CWebViewPlugin_Progress(void *instance);
+ BOOL _CWebViewPlugin_CanGoBack(void *instance);
+ BOOL _CWebViewPlugin_CanGoForward(void *instance);
+ void _CWebViewPlugin_GoBack(void *instance);
+ void _CWebViewPlugin_GoForward(void *instance);
+ void _CWebViewPlugin_Reload(void *instance);
+ void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
+ void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCustomHeader(void *instance);
+ void _CWebViewPlugin_ClearCookie(const char *url, const char *name);
+ void _CWebViewPlugin_ClearCookies();
+ void _CWebViewPlugin_SaveCookies();
+ void _CWebViewPlugin_GetCookies(void *instance, const char *url);
+ const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
+ void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password);
+ void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles);
+ void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended);
+}
+
+BOOL _CWebViewPlugin_IsInitialized(void *instance)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin isInitialized];
+}
+
+void *_CWebViewPlugin_Init(const char *gameObjectName, BOOL transparent, BOOL zoom, const char *ua, BOOL enableWKWebView, int contentMode, BOOL allowsLinkPreview, BOOL allowsBackForwardNavigationGestures, int radius)
+{
+ WKContentMode wkContentMode = WKContentModeRecommended;
+ switch (contentMode) {
+ case 1:
+ wkContentMode = WKContentModeMobile;
+ break;
+ case 2:
+ wkContentMode = WKContentModeDesktop;
+ break;
+ default:
+ wkContentMode = WKContentModeRecommended;
+ break;
+ }
+ CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObjectName:gameObjectName transparent:transparent zoom:zoom ua:ua enableWKWebView:enableWKWebView contentMode:wkContentMode allowsLinkPreview:allowsLinkPreview allowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures radius:radius];
+ [_instances addObject:webViewPlugin];
+ return (__bridge_retained void *)webViewPlugin;
+}
+
+void _CWebViewPlugin_Destroy(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
+ [_instances removeObject:webViewPlugin];
+ [webViewPlugin dispose];
+ webViewPlugin = nil;
+}
+
+void _CWebViewPlugin_SetMargins(
+ void *instance, float left, float top, float right, float bottom, BOOL relative)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setMargins:left top:top right:right bottom:bottom relative:relative];
+}
+
+void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetInteractionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setInteractionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setGoogleAppRedirectionEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetAlertDialogEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setAlertDialogEnabled:enabled];
+}
+
+void _CWebViewPlugin_SetScrollbarsVisibility(void *instance, BOOL visibility)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollbarsVisibility:visibility];
+}
+
+void _CWebViewPlugin_SetScrollBounceEnabled(void *instance, BOOL enabled)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setScrollBounceEnabled:enabled];
+}
+
+BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
+}
+
+void _CWebViewPlugin_LoadURL(void *instance, const char *url)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadURL:url];
+}
+
+void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadHTML:html baseURL:baseUrl];
+}
+
+void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin evaluateJS:js];
+}
+
+int _CWebViewPlugin_Progress(void *instance)
+{
+ if (instance == NULL)
+ return 0;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin progress];
+}
+
+BOOL _CWebViewPlugin_CanGoBack(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoBack];
+}
+
+BOOL _CWebViewPlugin_CanGoForward(void *instance)
+{
+ if (instance == NULL)
+ return false;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoForward];
+}
+
+void _CWebViewPlugin_GoBack(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goBack];
+}
+
+void _CWebViewPlugin_GoForward(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goForward];
+}
+
+void _CWebViewPlugin_Reload(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin reload];
+}
+
+void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
+}
+
+void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin removeCustomRequestHeader:headerKey];
+}
+
+void _CWebViewPlugin_ClearCustomHeader(void *instance)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCustomRequestHeader];
+}
+
+void _CWebViewPlugin_ClearCookie(const char *url, const char *name)
+{
+ [CWebViewPlugin clearCookie:name of:url];
+}
+
+void _CWebViewPlugin_ClearCookies()
+{
+ [CWebViewPlugin clearCookies];
+}
+
+void _CWebViewPlugin_SaveCookies()
+{
+ [CWebViewPlugin saveCookies];
+}
+
+void _CWebViewPlugin_GetCookies(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin getCookies:url];
+}
+
+const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
+{
+ if (instance == NULL)
+ return NULL;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin getCustomRequestHeaderValue:headerKey];
+}
+
+void _CWebViewPlugin_SetBasicAuthInfo(void *instance, const char *userName, const char *password)
+{
+ if (instance == NULL)
+ return;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setBasicAuthInfo:userName password:password];
+}
+
+void _CWebViewPlugin_ClearCache(void *instance, BOOL includeDiskFiles)
+{
+ // no op
+}
+
+void _CWebViewPlugin_SetSuspended(void *instance, BOOL suspended)
+{
+ // no op
+}
+#endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
diff --git a/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta b/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta
new file mode 100644
index 00000000..f5e0fb85
--- /dev/null
+++ b/dist/package/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: fc99cbfa2b53248b18d60e327b478581
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib b/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib
new file mode 100644
index 00000000..95777383
--- /dev/null
+++ b/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib
@@ -0,0 +1,31 @@
+mergeInto(LibraryManager.library, {
+ _gree_unity_webview_init: function(name) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.init(stringify(name));
+ },
+
+ _gree_unity_webview_setMargins: function (name, left, top, right, bottom) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.setMargins(stringify(name), left, top, right, bottom);
+ },
+
+ _gree_unity_webview_setVisibility: function(name, visible) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.setVisibility(stringify(name), visible);
+ },
+
+ _gree_unity_webview_loadURL: function(name, url) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.loadURL(stringify(name), stringify(url));
+ },
+
+ _gree_unity_webview_evaluateJS: function(name, js) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.evaluateJS(stringify(name), stringify(js));
+ },
+
+ _gree_unity_webview_destroy: function(name) {
+ var stringify = (UTF8ToString === undefined) ? Pointer_stringify : UTF8ToString;
+ unityWebView.destroy(stringify(name));
+ },
+});
diff --git a/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta b/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta
new file mode 100644
index 00000000..2e24b029
--- /dev/null
+++ b/dist/package/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 1353be0798ab043d992cd72e4d92970b
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ WebGL: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates.meta b/dist/package/Assets/WebGLTemplates.meta
new file mode 100644
index 00000000..1df8df3d
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 396d2c966866e4a8ca47369a69d03109
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview-2020.meta b/dist/package/Assets/WebGLTemplates/unity-webview-2020.meta
new file mode 100644
index 00000000..756a586c
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview-2020.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d9e103622e8c14154a1cd918fb92795e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html b/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html
new file mode 100644
index 00000000..84f13023
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Unity WebGL Player | {{{ PRODUCT_NAME }}}
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html.meta b/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html.meta
new file mode 100644
index 00000000..512f42c4
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview-2020/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7be04fa587d934a5c958c8fc02a10c40
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js b/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js
new file mode 100644
index 00000000..6a9c777f
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js
@@ -0,0 +1,103 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .appendTo($('#unity-container'));
+ }
+ var $last = $('.webviewContainer:last');
+ var clonedTop = parseInt($last.css('top')) - 100;
+ var $clone = $last.clone().insertAfter($last).css('top', clonedTop + '%');
+ var $iframe =
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
+ });
+ },
+
+ sendMessage: function (name, message) {
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var container = $('#unity-container');
+ var r = (container.hasClass('unity-desktop')) ? window.devicePixelRatio : 1;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
+ var canvas = $('#unity-canvas');
+ var w1 = canvas.attr('width');
+ var h1 = canvas.attr('height');
+
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w1 - left - right) / w0 * 100;
+ var hp = (h1 - top - bottom) / h0 * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta b/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta
new file mode 100644
index 00000000..8ee7ab8c
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5b98600d622f440fab913c56685e11bf
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview.meta b/dist/package/Assets/WebGLTemplates/unity-webview.meta
new file mode 100644
index 00000000..7c3eabcf
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 74f24ca1a6cc14b5c8da7e2a8e5de817
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview/index.html b/dist/package/Assets/WebGLTemplates/unity-webview/index.html
new file mode 100644
index 00000000..7474f1c4
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+ Unity Web Player
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview/index.html.meta b/dist/package/Assets/WebGLTemplates/unity-webview/index.html.meta
new file mode 100644
index 00000000..5b8b18d1
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: cd45543727a7e47d88051ca9ab86a6f5
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js b/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js
new file mode 100644
index 00000000..64f473c0
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js
@@ -0,0 +1,100 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .appendTo($('#gameContainer'));
+ }
+ var $last = $('.webviewContainer:last');
+ var clonedTop = parseInt($last.css('top')) - 100;
+ var $clone = $last.clone().insertAfter($last).css('top', clonedTop + '%');
+ var $iframe =
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
+ });
+ },
+
+ sendMessage: function (name, message) {
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var container = $('#gameContainer');
+ var r = window.devicePixelRatio;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
+
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w0 - left - right) / w0 * 100;
+ var hp = (h0 - top - bottom) / h0 * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta b/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta
new file mode 100644
index 00000000..b421d598
--- /dev/null
+++ b/dist/package/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8de1ecd3ea5954800b53548d8c2e2d70
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebPlayerTemplates.meta b/dist/package/Assets/WebPlayerTemplates.meta
new file mode 100644
index 00000000..d1d66623
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1ec4661d3c10c4d0e8b5e54ce1e46aa5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview.meta b/dist/package/Assets/WebPlayerTemplates/unity-webview.meta
new file mode 100644
index 00000000..06794cb6
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e76c9a30b7f6447eca50079ce4d595ed
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html b/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html
new file mode 100644
index 00000000..511e1137
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html
@@ -0,0 +1,136 @@
+
+
+
+
+ Unity Web Player | %UNITY_WEB_NAME%
+ %UNITY_UNITYOBJECT_DEPENDENCIES%
+
+
+
+
+
+
+ %UNITY_BETA_WARNING%
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html.meta b/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html.meta
new file mode 100644
index 00000000..fcb667ad
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview/index.html.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f6333062aa8e346f2abc78ef7a457580
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png b/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png
new file mode 100644
index 00000000..773c2e2d
Binary files /dev/null and b/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png differ
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta b/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta
new file mode 100644
index 00000000..b1c973ba
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 018355354713f41b2bed252c88e082c7
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js b/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js
new file mode 100644
index 00000000..f2ebe5fb
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js
@@ -0,0 +1,99 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ u.getUnity().SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ } else {
+ w.location.replace(href);
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ u.getUnity().SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+ });
+ },
+
+ sendMessage: function (name, message) {
+ u.getUnity().SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var $player = $('#unityPlayer');
+ var width = $player.width();
+ var height = $player.height();
+
+ var lp = left / width * 100;
+ var tp = top / height * 100;
+ var wp = (width - left - right) / width * 100;
+ var hp = (height - top - bottom) / height * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta b/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta
new file mode 100644
index 00000000..ac69546e
--- /dev/null
+++ b/dist/package/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7bf88e2aa1e624d64b530ad0c2383b9e
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/package.json b/dist/package/package.json
new file mode 100644
index 00000000..6366839c
--- /dev/null
+++ b/dist/package/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "net.gree.unity-webview",
+ "displayName": "unity-webview",
+ "version": "1.0.0",
+ "unity": "2019.1",
+ "description": "A plugin to display native webview views.",
+ "dependencies": {}
+}
diff --git a/dist/package/package.json.meta b/dist/package/package.json.meta
new file mode 100644
index 00000000..ab921bc5
--- /dev/null
+++ b/dist/package/package.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d863dd473116e4930a3d4f7444365973
+PackageManifestImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/package/unity-webview.asmdef b/dist/package/unity-webview.asmdef
new file mode 100644
index 00000000..de6eaec9
--- /dev/null
+++ b/dist/package/unity-webview.asmdef
@@ -0,0 +1,30 @@
+{
+ "name": "unity-webview",
+ "rootNamespace": "",
+ "references": [
+ "Unity.InputSystem"
+ ],
+ "includePlatforms": [
+ "Android",
+ "Editor",
+ "iOS",
+ "macOSStandalone",
+ "WebGL",
+ "WindowsStandalone32",
+ "WindowsStandalone64"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [
+ {
+ "name": "com.unity.inputsystem",
+ "expression": "",
+ "define": "UNITYWEBVIEW_USE_INPUT_SYSTEM"
+ }
+ ],
+ "noEngineReferences": false
+}
diff --git a/dist/package/unity-webview.asmdef.meta b/dist/package/unity-webview.asmdef.meta
new file mode 100644
index 00000000..102d91cc
--- /dev/null
+++ b/dist/package/unity-webview.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: df2a24f83ece042be84d3276a68393ed
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/dist/unity-webview-nofragment.unitypackage b/dist/unity-webview-nofragment.unitypackage
new file mode 100644
index 00000000..b71bfe00
Binary files /dev/null and b/dist/unity-webview-nofragment.unitypackage differ
diff --git a/dist/unity-webview-nofragment.zip b/dist/unity-webview-nofragment.zip
new file mode 100644
index 00000000..24ed2236
Binary files /dev/null and b/dist/unity-webview-nofragment.zip differ
diff --git a/dist/unity-webview.unitypackage b/dist/unity-webview.unitypackage
index c8be9561..f2515b5a 100644
Binary files a/dist/unity-webview.unitypackage and b/dist/unity-webview.unitypackage differ
diff --git a/dist/unity-webview.zip b/dist/unity-webview.zip
index 82cf72df..672ecd24 100644
Binary files a/dist/unity-webview.zip and b/dist/unity-webview.zip differ
diff --git a/doc/img/auto-graphics-api-setting-for-mac.png b/doc/img/auto-graphics-api-setting-for-mac.png
deleted file mode 100644
index 275c3322..00000000
Binary files a/doc/img/auto-graphics-api-setting-for-mac.png and /dev/null differ
diff --git a/doc/img/metal-editor-support-setting-for-mac.png b/doc/img/metal-editor-support-setting-for-mac.png
deleted file mode 100644
index 6157488c..00000000
Binary files a/doc/img/metal-editor-support-setting-for-mac.png and /dev/null differ
diff --git a/plugins/Android/build.gradle b/plugins/Android/build.gradle
index fe1e1e84..c2706edd 100644
--- a/plugins/Android/build.gradle
+++ b/plugins/Android/build.gradle
@@ -1,16 +1,22 @@
buildscript {
repositories {
- jcenter()
google()
+ mavenCentral()
+ jcenter().mavenContent {
+ includeGroup("org.jetbrains.trove4j")
+ }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.0'
+ classpath 'com.android.tools.build:gradle:4.1.0'
}
}
allprojects {
repositories {
- jcenter()
google()
+ mavenCentral()
+ jcenter().mavenContent {
+ includeGroup("org.jetbrains.trove4j")
+ }
}
}
diff --git a/plugins/Android/gradle.properties b/plugins/Android/gradle.properties
new file mode 100644
index 00000000..2f264041
--- /dev/null
+++ b/plugins/Android/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
diff --git a/plugins/Android/gradle/wrapper/gradle-wrapper.properties b/plugins/Android/gradle/wrapper/gradle-wrapper.properties
index 3e9d3abb..683e5249 100644
--- a/plugins/Android/gradle/wrapper/gradle-wrapper.properties
+++ b/plugins/Android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
diff --git a/plugins/Android/install.bat b/plugins/Android/install.bat
deleted file mode 100755
index 42fe22c7..00000000
--- a/plugins/Android/install.bat
+++ /dev/null
@@ -1,19 +0,0 @@
-@echo off
-
-rd /s /q bin
-rd /s /q gradle_build\libs
-rd /s /q gradle_build\src
-
-mkdir bin
-mkdir gradle_build\libs
-mkdir gradle_build\src
-mkdir gradle_build\src\main
-mkdir gradle_build\src\main\java
-
-copy /b "\Program Files\Unity5.6.1f1\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\classes.jar" gradle_build\libs >nul
-xcopy /s /e src gradle_build\src\main\java >nul
-copy /b AndroidManifest.xml gradle_build\src\main >nul
-
-call gradlew.bat assembleRelease
-
-jar cf bin\WebViewPlugin.jar -C gradle_build\build\intermediates\classes\release net
diff --git a/plugins/Android/install.sh b/plugins/Android/install.sh
index 68448520..b713833c 100755
--- a/plugins/Android/install.sh
+++ b/plugins/Android/install.sh
@@ -1,71 +1,123 @@
-#!/bin/sh
-
-# required command
-JAR_CMD=`which jar`
+#!/bin/bash
+set -euo pipefail
# directories
CWD=`dirname $0`
CWD=`cd $CWD && pwd -P`
-BUILD_DIR="${CWD}/gradle_build"
-LIBS_DIR="${BUILD_DIR}/libs"
-JAVA_DIR="${BUILD_DIR}/src/main/java"
-BIN_DIR="${CWD}/bin"
+case $(uname) in
+Darwin)
+ export JAVA_HOME='/Applications/Unity/Hub/Editor/2019.4.41f2/PlaybackEngines/AndroidPlayer/OpenJDK'
+ export ANDROID_SDK_ROOT='/Applications/Unity/Hub/Editor/2019.4.41f2/PlaybackEngines/AndroidPlayer/SDK'
+ export PATH=$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/tools/bin:$JAVA_HOME/bin:$PATH
+ ;;
+MINGW64_NT*)
+ export JAVA_HOME='/c/PROGRA~1/Unity/Hub/Editor/2019.4.41f2/Editor/Data/PlaybackEngines/AndroidPlayer/OpenJDK'
+ export ANDROID_SDK_ROOT='/c/PROGRA~1/Unity/Hub/Editor/2019.4.41f2/Editor/Data/PlaybackEngines/AndroidPlayer/SDK'
+ export PATH=$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/tools/bin:$JAVA_HOME/bin:$PATH
+ ;;
+esac
+DEST_DIR='../../build/Packager/Assets/Plugins/Android'
+
+if [ ! -d "$JAVA_HOME" ]
+then
+ echo 'From Unity Hub, please install 2019.4.41f2 with the android module.'
+ exit 1
+fi
# options
+TARGET="webview"
MODE="Release"
-SCRIPTING_BACKEND="il2cpp"
-UNITY="/Applications/Unity5.6.1f1"
+UNITY='2019.4.41f2'
+for OPT in $*
+do
+ case $OPT in
+ '--nofragment')
+ TARGET="webview-nofragment"
+ ;;
+ '--development')
+ MODE="Development"
+ ;;
+ '--zorderpatch')
+ UNITY='5.6.1f1'
+ ;;
+ *)
+ cat < ${TARGET}/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
+ ;;
+*)
+ dst=${DEST_DIR}/WebViewPlugin-development.aar.tmpl
+ cp -a $tmp/CWebViewPlugin.java ${TARGET}/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
+ ;;
+esac
+# remove CUnityPlayer*.java if UNITY != 5.6.1f1.
+case $UNITY in
+'5.6.1f1')
+ ;;
+*)
+ rm -f ${TARGET}/src/main/java/net/gree/unitywebview/CUnityPlayer*.java
+ ;;
+esac
-cp ${UNITY_JAVA_LIB} ${LIBS_DIR}
-cp -r src/net ${JAVA_DIR}
-cp AndroidManifest.xml ${BUILD_DIR}/src/main
+pushd $CWD
# build
-./gradlew assembleRelease
+cp "${UNITY_DIR}/PlaybackEngines/AndroidPlayer/Variations/il2cpp/${MODE}/Classes/classes.jar" ${TARGET}/libs
+./gradlew clean -p $TARGET
+./gradlew assembleRelease -p $TARGET
-if [ ${JAR_CMD} = "" ];then
- echo "jar command does not exist"
-else
- jar cvf ${CWD}/bin/WebViewPlugin.jar -C ${BUILD_DIR}/build/intermediates/classes/release net
- cp -a ${BIN_DIR}/WebViewPlugin.jar ${DEST_DIR}
-fi
-
-./gradlew clean
+# install
+mkdir -p ${DEST_DIR}
+echo cp ${TARGET}/build/outputs/aar/*.aar $dst
+cp ${TARGET}/build/outputs/aar/*.aar $dst
+case $TARGET in
+'webview')
+ core_aar=`basename ${TARGET}/libs-ext/core*.aar`
+ echo cp ${TARGET}/libs-ext/$core_aar ${DEST_DIR}/$core_aar.tmpl
+ cp ${TARGET}/libs-ext/$core_aar ${DEST_DIR}/$core_aar.tmpl
+ ;;
+esac
-popd # $BUILD_DIR
+popd
diff --git a/plugins/Android/settings.gradle b/plugins/Android/settings.gradle
index a821943a..3b363e56 100644
--- a/plugins/Android/settings.gradle
+++ b/plugins/Android/settings.gradle
@@ -1 +1,2 @@
-include ':gradle_build'
+include ':webview'
+include ':webview-nofragment'
diff --git a/plugins/Android/src/net/gree/unitywebview/CWebViewPlugin.java b/plugins/Android/src/net/gree/unitywebview/CWebViewPlugin.java
deleted file mode 100644
index 2f5fd5b4..00000000
--- a/plugins/Android/src/net/gree/unitywebview/CWebViewPlugin.java
+++ /dev/null
@@ -1,562 +0,0 @@
-/*
- * Copyright (C) 2011 Keijiro Takahashi
- * Copyright (C) 2012 GREE, Inc.
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- */
-
-package net.gree.unitywebview;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.net.Uri;
-import android.os.Build;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.webkit.JavascriptInterface;
-import android.webkit.WebChromeClient;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
-import android.webkit.WebSettings;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
-import android.widget.FrameLayout;
-import android.webkit.PermissionRequest;
-// import android.support.v4.app.ActivityCompat;
-
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-
-import com.unity3d.player.UnityPlayer;
-
-class CWebViewPluginInterface {
- private CWebViewPlugin mPlugin;
- private String mGameObject;
-
- public CWebViewPluginInterface(CWebViewPlugin plugin, String gameObject) {
- mPlugin = plugin;
- mGameObject = gameObject;
- }
-
- @JavascriptInterface
- public void call(final String message) {
- call("CallFromJS", message);
- }
-
- public void call(final String method, final String message) {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mPlugin.IsInitialized()) {
- UnityPlayer.UnitySendMessage(mGameObject, method, message);
- }
- }});
- }
-}
-
-public class CWebViewPlugin {
- private static FrameLayout layout = null;
- private WebView mWebView;
- private OnGlobalLayoutListener mGlobalLayoutListener;
- private CWebViewPluginInterface mWebViewPlugin;
- private int progress;
- private boolean canGoBack;
- private boolean canGoForward;
- private Hashtable mCustomHeaders;
- private String mWebViewUA;
-
- public CWebViewPlugin() {
- }
-
- public static boolean IsWebViewAvailable() {
- final Activity a = UnityPlayer.currentActivity;
- FutureTask t = new FutureTask(new Callable() {
- public Boolean call() throws Exception {
- boolean isAvailable = false;
- try {
- WebView webView = new WebView(a);
- if (webView != null) {
- webView = null;
- isAvailable = true;
- }
- } catch (Exception e) {
- }
- return isAvailable;
- }
- });
- a.runOnUiThread(t);
- try {
- return t.get();
- } catch (Exception e) {
- return false;
- }
- }
-
- public boolean IsInitialized() {
- return mWebView != null;
- }
-
- public void Init(final String gameObject, final boolean transparent, final String ua) {
- final CWebViewPlugin self = this;
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView != null) {
- return;
- }
- mCustomHeaders = new Hashtable();
-
- final WebView webView = new WebView(a);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- try {
- ApplicationInfo ai = a.getPackageManager().getApplicationInfo(a.getPackageName(), 0);
- if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- webView.setWebContentsDebuggingEnabled(true);
- }
- } catch (Exception ex) {
- }
- }
- webView.setVisibility(View.GONE);
- webView.setFocusable(true);
- webView.setFocusableInTouchMode(true);
-
- // webView.setWebChromeClient(new WebChromeClient() {
- // public boolean onConsoleMessage(android.webkit.ConsoleMessage cm) {
- // Log.d("Webview", cm.message());
- // return true;
- // }
- // });
- webView.setWebChromeClient(new WebChromeClient() {
- View videoView;
-
- // cf. https://stackoverflow.com/questions/40659198/how-to-access-the-camera-from-within-a-webview/47525818#47525818
- // cf. https://github.com/googlesamples/android-PermissionRequest/blob/eff1d21f0b9c91d67c7f2a2303b591447e61e942/Application/src/main/java/com/example/android/permissionrequest/PermissionRequestFragment.java#L148-L161
- @Override
- public void onPermissionRequest(final PermissionRequest request) {
- final String[] requestedResources = request.getResources();
- for (String r : requestedResources) {
- if (r.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE) || r.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
- request.grant(requestedResources);
- // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- // a.runOnUiThread(new Runnable() {public void run() {
- // final String[] permissions = {
- // "android.permission.CAMERA",
- // "android.permission.RECORD_AUDIO",
- // };
- // ActivityCompat.requestPermissions(a, permissions, 0);
- // }});
- // }
- break;
- }
- }
- }
-
- @Override
- public void onProgressChanged(WebView view, int newProgress) {
- progress = newProgress;
- }
-
- @Override
- public void onShowCustomView(View view, CustomViewCallback callback) {
- super.onShowCustomView(view, callback);
- if (layout != null) {
- videoView = view;
- layout.setBackgroundColor(0xff000000);
- layout.addView(videoView);
- }
- }
-
- @Override
- public void onHideCustomView() {
- super.onHideCustomView();
- if (layout != null) {
- layout.removeView(videoView);
- layout.setBackgroundColor(0x00000000);
- videoView = null;
- }
- }
- });
-
- mWebViewPlugin = new CWebViewPluginInterface(self, gameObject);
- webView.setWebViewClient(new WebViewClient() {
- @Override
- public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- webView.loadUrl("about:blank");
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- mWebViewPlugin.call("CallOnError", errorCode + "\t" + description + "\t" + failingUrl);
- }
-
- @Override
- public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- mWebViewPlugin.call("CallOnHttpError", Integer.toString(errorResponse.getStatusCode()));
- }
-
- @Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- mWebViewPlugin.call("CallOnStarted", url);
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- mWebViewPlugin.call("CallOnLoaded", url);
- }
-
- @Override
- public void onLoadResource(WebView view, String url) {
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- }
-
- @Override
- public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
- if (mCustomHeaders == null || mCustomHeaders.isEmpty()) {
- return super.shouldInterceptRequest(view, url);
- }
-
- try {
- HttpURLConnection urlCon = (HttpURLConnection) (new URL(url)).openConnection();
- // The following should make HttpURLConnection have a same user-agent of webView)
- // cf. http://d.hatena.ne.jp/faw/20070903/1188796959 (in Japanese)
- urlCon.setRequestProperty("User-Agent", mWebViewUA);
-
- for (HashMap.Entry entry: mCustomHeaders.entrySet()) {
- urlCon.setRequestProperty(entry.getKey(), entry.getValue());
- }
-
- urlCon.connect();
-
- return new WebResourceResponse(
- urlCon.getContentType().split(";", 2)[0],
- urlCon.getContentEncoding(),
- urlCon.getInputStream()
- );
-
- } catch (Exception e) {
- return super.shouldInterceptRequest(view, url);
- }
- }
-
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- canGoBack = webView.canGoBack();
- canGoForward = webView.canGoForward();
- if (url.startsWith("http://") || url.startsWith("https://")
- || url.startsWith("file://") || url.startsWith("javascript:")) {
- // Let webview handle the URL
- return false;
- } else if (url.startsWith("unity:")) {
- String message = url.substring(6);
- mWebViewPlugin.call("CallFromJS", message);
- return true;
- }
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- PackageManager pm = a.getPackageManager();
- List apps = pm.queryIntentActivities(intent, 0);
- if (apps.size() > 0) {
- view.getContext().startActivity(intent);
- }
- return true;
- }
- });
- webView.addJavascriptInterface(mWebViewPlugin , "Unity");
-
- WebSettings webSettings = webView.getSettings();
- if (ua != null && ua.length() > 0) {
- webSettings.setUserAgentString(ua);
- }
- mWebViewUA = webSettings.getUserAgentString();
- webSettings.setSupportZoom(true);
- webSettings.setBuiltInZoomControls(true);
- webSettings.setDisplayZoomControls(false);
- webSettings.setLoadWithOverviewMode(true);
- webSettings.setUseWideViewPort(true);
- webSettings.setJavaScriptEnabled(true);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- // Log.i("CWebViewPlugin", "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
- webSettings.setAllowUniversalAccessFromFileURLs(true);
- }
- if (android.os.Build.VERSION.SDK_INT >= 17) {
- webSettings.setMediaPlaybackRequiresUserGesture(false);
- }
- webSettings.setDatabaseEnabled(true);
- webSettings.setDomStorageEnabled(true);
- String databasePath = webView.getContext().getDir("databases", Context.MODE_PRIVATE).getPath();
- webSettings.setDatabasePath(databasePath);
-
- if (transparent) {
- webView.setBackgroundColor(0x00000000);
- }
-
- if (layout == null || layout.getParent() != a.findViewById(android.R.id.content)) {
- layout = new FrameLayout(a);
- a.addContentView(
- layout,
- new LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- layout.setFocusable(true);
- layout.setFocusableInTouchMode(true);
- }
- layout.addView(
- webView,
- new FrameLayout.LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- Gravity.NO_GRAVITY));
- mWebView = webView;
- }});
-
- final View activityRootView = a.getWindow().getDecorView().getRootView();
- mGlobalLayoutListener = new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- android.graphics.Rect r = new android.graphics.Rect();
- //r will be populated with the coordinates of your view that area still visible.
- activityRootView.getWindowVisibleDisplayFrame(r);
- android.view.Display display = a.getWindowManager().getDefaultDisplay();
- // cf. http://stackoverflow.com/questions/9654016/getsize-giving-me-errors/10564149#10564149
- int h = 0;
- try {
- Point size = new Point();
- display.getSize(size);
- h = size.y;
- } catch (java.lang.NoSuchMethodError err) {
- h = display.getHeight();
- }
- int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
- //System.out.print(String.format("[NativeWebview] %d, %d\n", h, heightDiff));
- if (heightDiff > h / 3) { // assume that this means that the keyboard is on
- UnityPlayer.UnitySendMessage(gameObject, "SetKeyboardVisible", "true");
- } else {
- UnityPlayer.UnitySendMessage(gameObject, "SetKeyboardVisible", "false");
- }
- }
- };
- activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
- }
-
- public void Destroy() {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- if (mGlobalLayoutListener != null) {
- View activityRootView = a.getWindow().getDecorView().getRootView();
- activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
- mGlobalLayoutListener = null;
- }
- mWebView.stopLoading();
- layout.removeView(mWebView);
- mWebView.destroy();
- mWebView = null;
- }});
- }
-
- public void LoadURL(final String url) {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- if (mCustomHeaders != null && !mCustomHeaders.isEmpty()) {
- mWebView.loadUrl(url, mCustomHeaders);
- } else {
- mWebView.loadUrl(url);;
- }
- }});
- }
-
- public void LoadHTML(final String html, final String baseURL)
- {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- mWebView.loadDataWithBaseURL(baseURL, html, "text/html", "UTF8", null);
- }});
- }
-
- public void EvaluateJS(final String js) {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- mWebView.evaluateJavascript(js, null);
- } else {
- mWebView.loadUrl("javascript:" + URLEncoder.encode(js));
- }
- }});
- }
-
- public void GoBack() {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- mWebView.goBack();
- }});
- }
-
- public void GoForward() {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- mWebView.goForward();
- }});
- }
-
- public void SetMargins(int left, int top, int right, int bottom) {
- final FrameLayout.LayoutParams params
- = new FrameLayout.LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- Gravity.NO_GRAVITY);
- params.setMargins(left, top, right, bottom);
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- mWebView.setLayoutParams(params);
- }});
- }
-
- public void SetVisibility(final boolean visibility) {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- if (visibility) {
- mWebView.setVisibility(View.VISIBLE);
- layout.requestFocus();
- mWebView.requestFocus();
- } else {
- mWebView.setVisibility(View.GONE);
- }
- }});
- }
-
- // cf. https://stackoverflow.com/questions/31788748/webview-youtube-videos-playing-in-background-on-rotation-and-minimise/31789193#31789193
- public void OnApplicationPause(final boolean paused) {
- final Activity a = UnityPlayer.currentActivity;
- a.runOnUiThread(new Runnable() {public void run() {
- if (mWebView == null) {
- return;
- }
- if (paused) {
- mWebView.onPause();
- mWebView.pauseTimers();
- } else {
- mWebView.onResume();
- mWebView.resumeTimers();
- }
- }});
- }
-
- public void AddCustomHeader(final String headerKey, final String headerValue)
- {
- if (mCustomHeaders == null) {
- return;
- }
- mCustomHeaders.put(headerKey, headerValue);
- }
-
- public String GetCustomHeaderValue(final String headerKey)
- {
- if (mCustomHeaders == null) {
- return null;
- }
-
- if (!mCustomHeaders.containsKey(headerKey)) {
- return null;
- }
- return this.mCustomHeaders.get(headerKey);
- }
-
- public void RemoveCustomHeader(final String headerKey)
- {
- if (mCustomHeaders == null) {
- return;
- }
-
- if (this.mCustomHeaders.containsKey(headerKey)) {
- this.mCustomHeaders.remove(headerKey);
- }
- }
-
- public void ClearCustomHeader()
- {
- if (mCustomHeaders == null) {
- return;
- }
-
- this.mCustomHeaders.clear();
- }
-
- public void ClearCookies()
- {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
- {
- CookieManager.getInstance().removeAllCookies(null);
- CookieManager.getInstance().flush();
- } else {
- final Activity a = UnityPlayer.currentActivity;
- CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
- cookieSyncManager.startSync();
- CookieManager cookieManager = CookieManager.getInstance();
- cookieManager.removeAllCookie();
- cookieManager.removeSessionCookie();
- cookieSyncManager.stopSync();
- cookieSyncManager.sync();
- }
- }
-
- public String GetCookies(String url)
- {
- CookieManager cookieManager = CookieManager.getInstance();
- return cookieManager.getCookie(url);
- }
-}
diff --git a/plugins/Android/gradle_build/build.gradle b/plugins/Android/webview-nofragment/build.gradle
similarity index 64%
rename from plugins/Android/gradle_build/build.gradle
rename to plugins/Android/webview-nofragment/build.gradle
index 481c28cb..e10c6788 100644
--- a/plugins/Android/gradle_build/build.gradle
+++ b/plugins/Android/webview-nofragment/build.gradle
@@ -1,18 +1,15 @@
apply plugin: 'com.android.library'
-dependencies {
- compileOnly files('./libs/classes.jar')
-}
-
android {
- compileSdkVersion 23
- buildToolsVersion "28.0.2"
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 16
- targetSdkVersion 23
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
+ consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
@@ -22,3 +19,6 @@ android {
}
}
+dependencies {
+ compileOnly fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/plugins/Android/webview-nofragment/consumer-rules.pro b/plugins/Android/webview-nofragment/consumer-rules.pro
new file mode 100644
index 00000000..8a85ff47
--- /dev/null
+++ b/plugins/Android/webview-nofragment/consumer-rules.pro
@@ -0,0 +1,3 @@
+-keep class net.gree.unitywebview.** {
+ *;
+}
diff --git a/plugins/Android/webview-nofragment/libs/.gitkeep b/plugins/Android/webview-nofragment/libs/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/Android/webview-nofragment/proguard-rules.pro b/plugins/Android/webview-nofragment/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/plugins/Android/webview-nofragment/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/plugins/Android/AndroidManifest.xml b/plugins/Android/webview-nofragment/src/main/AndroidManifest.xml
similarity index 100%
rename from plugins/Android/AndroidManifest.xml
rename to plugins/Android/webview-nofragment/src/main/AndroidManifest.xml
diff --git a/plugins/Android/src/net/gree/unitywebview/CUnityPlayer.java b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CUnityPlayer.java
similarity index 100%
rename from plugins/Android/src/net/gree/unitywebview/CUnityPlayer.java
rename to plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CUnityPlayer.java
diff --git a/plugins/Android/src/net/gree/unitywebview/CUnityPlayerActivity.java b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CUnityPlayerActivity.java
similarity index 100%
rename from plugins/Android/src/net/gree/unitywebview/CUnityPlayerActivity.java
rename to plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CUnityPlayerActivity.java
diff --git a/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CWebViewPlugin.java b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
new file mode 100644
index 00000000..dc6d3a15
--- /dev/null
+++ b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
@@ -0,0 +1,1178 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+package net.gree.unitywebview;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+//#if UNITYWEBVIEW_DEVELOPMENT
+import android.content.pm.ApplicationInfo;
+//#endif
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Base64;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.webkit.GeolocationPermissions.Callback;
+import android.webkit.HttpAuthHandler;
+import android.webkit.JavascriptInterface;
+import android.webkit.JsResult;
+import android.webkit.JsPromptResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.widget.FrameLayout;
+import android.webkit.PermissionRequest;
+
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.unity3d.player.UnityPlayer;
+
+class CWebViewPluginInterface {
+ private CWebViewPlugin mPlugin;
+ private String mGameObject;
+
+ public CWebViewPluginInterface(CWebViewPlugin plugin, String gameObject) {
+ mPlugin = plugin;
+ mGameObject = gameObject;
+ }
+
+ @JavascriptInterface
+ public void call(final String message) {
+ call("CallFromJS", message);
+ }
+
+ public void call(final String method, final String message) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mPlugin.IsInitialized()) {
+ mPlugin.MyUnitySendMessage(mGameObject, method, message);
+ }
+ }});
+ }
+}
+
+public class CWebViewPlugin {
+ private static boolean forceBringToFront;
+ private static FrameLayout layout = null;
+ private Queue mMessages = new ArrayDeque();
+ private WebView mWebView;
+ private View mVideoView;
+ private OnGlobalLayoutListener mGlobalLayoutListener;
+ private CWebViewPluginInterface mWebViewPlugin;
+ private int progress;
+ private boolean canGoBack;
+ private boolean canGoForward;
+ private boolean mInteractionEnabled = true;
+ private boolean mGoogleAppRedirectionEnabled;
+ private boolean mAlertDialogEnabled;
+ private boolean mAllowVideoCapture;
+ private boolean mAllowAudioCapture;
+ private Hashtable mCustomHeaders;
+ private String mWebViewUA;
+ private Pattern mAllowRegex;
+ private Pattern mDenyRegex;
+ private Pattern mHookRegex;
+
+ private String mBasicAuthUserName;
+ private String mBasicAuthPassword;
+
+ // cf. https://chromium.googlesource.com/chromium/src/+/3e5a94daf32200d65dea6072dd4d1b9a2025508b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java#121
+ private static final int ALLOWED_INTENT_FLAGS
+ = Intent.FLAG_EXCLUDE_STOPPED_PACKAGES
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS
+ | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+
+ // cf. https://chromium.googlesource.com/chromium/src/+/3e5a94daf32200d65dea6072dd4d1b9a2025508b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java#1808
+ private static void sanitizeQueryIntentActivitiesIntent(Intent intent) {
+ intent.setFlags(intent.getFlags() & ALLOWED_INTENT_FLAGS);
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.setComponent(null);
+
+ // Intent Selectors allow intents to bypass the intent filter and potentially send apps URIs
+ // they were not expecting to handle. https://crbug.com/1254422
+ intent.setSelector(null);
+ }
+
+ // cf. https://github.com/gree/unity-webview/issues/753
+ // cf. https://github.com/mixpanel/mixpanel-android/issues/400
+ // cf. https://github.com/mixpanel/mixpanel-android/commit/98bb530f9263f3bac0737971acc00dfef7ea4c35
+ public static boolean isDestroyed(final Activity a) {
+ if (a == null) {
+ return true;
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return a.isDestroyed();
+ } else {
+ return false;
+ }
+ }
+
+ public CWebViewPlugin() {
+ }
+
+ public static boolean IsWebViewAvailable() {
+ final Activity a = UnityPlayer.currentActivity;
+ FutureTask t = new FutureTask(new Callable() {
+ public Boolean call() throws Exception {
+ boolean isAvailable = false;
+ try {
+ WebView webView = new WebView(a);
+ if (webView != null) {
+ webView = null;
+ isAvailable = true;
+ }
+ } catch (Exception e) {
+ }
+ return isAvailable;
+ }
+ });
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return false;
+ }
+ a.runOnUiThread(t);
+ try {
+ return t.get();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public String GetMessage() {
+ synchronized(mMessages) {
+ return (mMessages.size() > 0) ? mMessages.poll() : null;
+ }
+ }
+
+ public void MyUnitySendMessage(String gameObject, String method, String message) {
+ synchronized(mMessages) {
+ mMessages.add(method + ":" + message);
+ }
+ }
+
+ public boolean IsInitialized() {
+ return mWebView != null;
+ }
+
+ public void Init(final String gameObject, final boolean transparent, final boolean zoom, final int androidForceDarkMode, final String ua, final int radius) {
+ final CWebViewPlugin self = this;
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView != null) {
+ return;
+ }
+ mAlertDialogEnabled = true;
+ mAllowVideoCapture = false;
+ mAllowAudioCapture = false;
+ mCustomHeaders = new Hashtable();
+
+ final WebView webView = (radius > 0) ? new RoundedWebView(a, radius) : new WebView(a);
+//#if UNITYWEBVIEW_DEVELOPMENT
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try {
+ ApplicationInfo ai = a.getPackageManager().getApplicationInfo(a.getPackageName(), 0);
+ if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ webView.setWebContentsDebuggingEnabled(true);
+ }
+ } catch (Exception ex) {
+ }
+ }
+//#endif
+ webView.setVisibility(View.GONE);
+ webView.setFocusable(true);
+ webView.setFocusableInTouchMode(true);
+
+ // webView.setWebChromeClient(new WebChromeClient() {
+ // public boolean onConsoleMessage(android.webkit.ConsoleMessage cm) {
+ // Log.d("Webview", cm.message());
+ // return true;
+ // }
+ // });
+ webView.setWebChromeClient(new WebChromeClient() {
+ // cf. https://stackoverflow.com/questions/40659198/how-to-access-the-camera-from-within-a-webview/47525818#47525818
+ // cf. https://github.com/googlesamples/android-PermissionRequest/blob/eff1d21f0b9c91d67c7f2a2303b591447e61e942/Application/src/main/java/com/example/android/permissionrequest/PermissionRequestFragment.java#L148-L161
+ @Override
+ public void onPermissionRequest(final PermissionRequest request) {
+ final String[] requestedResources = request.getResources();
+ for (String r : requestedResources) {
+ if ((r.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE) && mAllowVideoCapture)
+ || (r.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE) && mAllowAudioCapture)
+ || r.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
+ request.grant(requestedResources);
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // a.runOnUiThread(new Runnable() {public void run() {
+ // final String[] permissions = {
+ // "android.permission.CAMERA",
+ // "android.permission.RECORD_AUDIO",
+ // };
+ // ActivityCompat.requestPermissions(a, permissions, 0);
+ // }});
+ // }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ progress = newProgress;
+ }
+
+ @Override
+ public void onShowCustomView(View view, CustomViewCallback callback) {
+ super.onShowCustomView(view, callback);
+ if (layout != null) {
+ mVideoView = view;
+ layout.setBackgroundColor(0xff000000);
+ layout.addView(mVideoView);
+ }
+ }
+
+ @Override
+ public void onHideCustomView() {
+ super.onHideCustomView();
+ if (layout != null) {
+ layout.removeView(mVideoView);
+ layout.setBackgroundColor(0x00000000);
+ mVideoView = null;
+ }
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsAlert(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsConfirm(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+ callback.invoke(origin, true, false);
+ }
+ });
+
+ mWebViewPlugin = new CWebViewPluginInterface(self, gameObject);
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ webView.loadUrl("about:blank");
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnError", errorCode + "\t" + description + "\t" + failingUrl);
+ }
+
+ @Override
+ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnHttpError", Integer.toString(errorResponse.getStatusCode()));
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnStarted", url);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnLoaded", url);
+ }
+
+ @Override
+ public void onLoadResource(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+ if (mBasicAuthUserName != null && mBasicAuthPassword != null) {
+ handler.proceed(mBasicAuthUserName, mBasicAuthPassword);
+ } else {
+ handler.cancel();
+ }
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, final String url) {
+ if (mCustomHeaders == null || mCustomHeaders.isEmpty()) {
+ return super.shouldInterceptRequest(view, url);
+ }
+ return shouldInterceptRequest(view, url, null);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+ if (mCustomHeaders == null || mCustomHeaders.isEmpty()) {
+ return super.shouldInterceptRequest(view, request);
+ }
+ return shouldInterceptRequest(view, request.getUrl().toString(), request.getRequestHeaders());
+ }
+
+ public WebResourceResponse shouldInterceptRequest(WebView view, final String url, Map headers) {
+ try {
+ HttpURLConnection urlCon = (HttpURLConnection) (new URL(url)).openConnection();
+ urlCon.setInstanceFollowRedirects(false);
+ // The following should make HttpURLConnection have a same user-agent of webView)
+ // cf. http://d.hatena.ne.jp/faw/20070903/1188796959 (in Japanese)
+ urlCon.setRequestProperty("User-Agent", mWebViewUA);
+
+ if (mBasicAuthUserName != null && mBasicAuthPassword != null) {
+ String authorization = mBasicAuthUserName + ":" + mBasicAuthPassword;
+ urlCon.setRequestProperty("Authorization", "Basic " + Base64.encodeToString(authorization.getBytes(), Base64.NO_WRAP));
+ }
+
+ if (Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT_WATCH) {
+ // cf. https://issuetracker.google.com/issues/36989494
+ String cookies = CookieManager.getInstance().getCookie(url);
+ if (cookies != null && !cookies.isEmpty()) {
+ urlCon.addRequestProperty("Cookie", cookies);
+ }
+ }
+
+ if (headers != null) {
+ for (Map.Entry entry: headers.entrySet()) {
+ urlCon.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+ }
+ for (HashMap.Entry entry: mCustomHeaders.entrySet()) {
+ urlCon.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+
+ urlCon.connect();
+
+ int responseCode = urlCon.getResponseCode();
+ if (responseCode >= 300 && responseCode < 400) {
+ // To avoid a problem due to a mismatch between requested URL and returned content,
+ // make WebView request again in the case that redirection response was returned.
+ return null;
+ }
+
+ final List setCookieHeaders = urlCon.getHeaderFields().get("Set-Cookie");
+ if (setCookieHeaders != null) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT || Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT_WATCH) {
+ // In addition to getCookie, setCookie cause deadlock on Android 4.4.4 cf. https://issuetracker.google.com/issues/36989494
+ final Activity a = UnityPlayer.currentActivity;
+ if (!CWebViewPlugin.isDestroyed(a)) {
+ a.runOnUiThread(new Runnable() {
+ public void run() {
+ SetCookies(url, setCookieHeaders);
+ }
+ });
+ }
+ } else {
+ SetCookies(url, setCookieHeaders);
+ }
+ }
+
+ return new WebResourceResponse(
+ urlCon.getContentType().split(";", 2)[0],
+ urlCon.getContentEncoding(),
+ urlCon.getInputStream()
+ );
+
+ } catch (Exception e) {
+ return super.shouldInterceptRequest(view, url);
+ }
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ boolean pass = true;
+ if (mAllowRegex != null && mAllowRegex.matcher(url).find()) {
+ pass = true;
+ } else if (mDenyRegex != null && mDenyRegex.matcher(url).find()) {
+ pass = false;
+ }
+ if (!pass) {
+ return true;
+ }
+ if (url.startsWith("unity:")) {
+ String message = url.substring(6);
+ mWebViewPlugin.call("CallFromJS", message);
+ return true;
+ } else if (mHookRegex != null && mHookRegex.matcher(url).find()) {
+ mWebViewPlugin.call("CallOnHooked", url);
+ return true;
+ } else if (!mGoogleAppRedirectionEnabled && url.startsWith("https://www.google.com/")) {
+ mWebView.loadUrl(url);
+ return true;
+ } else if (!mGoogleAppRedirectionEnabled && url.startsWith("intent://www.google.com/")) {
+ return true;
+ } else if (!url.toLowerCase().endsWith(".pdf")
+ && !url.startsWith("https://maps.app.goo.gl")
+ && (url.startsWith("http://")
+ || url.startsWith("https://")
+ || url.startsWith("file://")
+ || url.startsWith("javascript:"))) {
+ mWebViewPlugin.call("CallOnStarted", url);
+ // Let webview handle the URL
+ return false;
+ } else if (url.startsWith("intent://") || url.startsWith("android-app://")) {
+ Intent intent = null;
+ try {
+ intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
+ // cf. https://www.m3tech.blog/entry/android-webview-intent-scheme
+ sanitizeQueryIntentActivitiesIntent(intent);
+ view.getContext().startActivity(intent);
+ } catch (URISyntaxException ex) {
+ } catch (ActivityNotFoundException ex) {
+ launchMarket(view.getContext(), intent);
+ }
+ return true;
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ // PackageManager pm = a.getPackageManager();
+ // List apps = pm.queryIntentActivities(intent, 0);
+ // if (apps.size() > 0) {
+ // view.getContext().startActivity(intent);
+ // }
+ try {
+ view.getContext().startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ }
+ return true;
+ }
+
+ private void launchMarket(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ String packageName = intent.getPackage();
+ if (packageName == null) {
+ return;
+ }
+ // cf. https://stackoverflow.com/questions/11753000/how-to-open-the-google-play-store-directly-from-my-android-application/11753070#11753070
+ try {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName));
+ context.startActivity(intent);
+ } catch (android.content.ActivityNotFoundException ex) {
+ try {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + packageName));
+ context.startActivity(intent);
+ } catch (android.content.ActivityNotFoundException ex2) {
+ }
+ }
+ }
+ });
+ webView.addJavascriptInterface(mWebViewPlugin , "Unity");
+
+ WebSettings webSettings = webView.getSettings();
+ if (ua != null && ua.length() > 0) {
+ webSettings.setUserAgentString(ua);
+ }
+ mWebViewUA = webSettings.getUserAgentString();
+ if (zoom) {
+ webSettings.setSupportZoom(true);
+ webSettings.setBuiltInZoomControls(true);
+ } else {
+ webSettings.setSupportZoom(false);
+ webSettings.setBuiltInZoomControls(false);
+ }
+ webSettings.setDisplayZoomControls(false);
+ webSettings.setLoadWithOverviewMode(true);
+ webSettings.setUseWideViewPort(true);
+ webSettings.setJavaScriptEnabled(true);
+ webSettings.setGeolocationEnabled(true);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // Log.i("CWebViewPlugin", "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
+ webSettings.setAllowUniversalAccessFromFileURLs(true);
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 17) {
+ webSettings.setMediaPlaybackRequiresUserGesture(false);
+ }
+ webSettings.setDatabaseEnabled(true);
+ webSettings.setDomStorageEnabled(true);
+ String databasePath = webView.getContext().getDir("databases", Context.MODE_PRIVATE).getPath();
+ webSettings.setDatabasePath(databasePath);
+ webSettings.setAllowFileAccess(true); // cf. https://github.com/gree/unity-webview/issues/625
+
+ // cf. https://forum.unity.com/threads/unity-ios-dark-mode.805344/#post-6476051
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ switch (androidForceDarkMode) {
+ case 0:
+ {
+ Configuration configuration = UnityPlayer.currentActivity.getResources().getConfiguration();
+ switch (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
+ case Configuration.UI_MODE_NIGHT_NO:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_OFF);
+ break;
+ case Configuration.UI_MODE_NIGHT_YES:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
+ break;
+ }
+ }
+ break;
+ case 1:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_OFF);
+ break;
+ case 2:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
+ break;
+ }
+ }
+
+ if (transparent) {
+ webView.setBackgroundColor(0x00000000);
+ }
+
+ // cf. https://stackoverflow.com/questions/3853794/disable-webview-touch-events-in-android/3856199#3856199
+ webView.setOnTouchListener(
+ new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ return !mInteractionEnabled;
+ }
+ });
+
+ if (layout == null || layout.getParent() != a.findViewById(android.R.id.content)) {
+ layout = new FrameLayout(a);
+ a.addContentView(
+ layout,
+ new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ layout.setFocusable(true);
+ layout.setFocusableInTouchMode(true);
+ }
+ layout.addView(
+ webView,
+ new FrameLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT,
+ Gravity.NO_GRAVITY));
+ mWebView = webView;
+ }});
+
+ final View activityRootView = a.getWindow().getDecorView().getRootView();
+ mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ android.graphics.Rect r = new android.graphics.Rect();
+ //r will be populated with the coordinates of your view that area still visible.
+ activityRootView.getWindowVisibleDisplayFrame(r);
+ android.view.Display display = a.getWindowManager().getDefaultDisplay();
+ // cf. http://stackoverflow.com/questions/9654016/getsize-giving-me-errors/10564149#10564149
+ int h = 0;
+ try {
+ Point size = new Point();
+ display.getSize(size);
+ h = size.y;
+ } catch (java.lang.NoSuchMethodError err) {
+ h = display.getHeight();
+ }
+
+ // View rootView = activityRootView.getRootView();
+ // int bottomPadding = 0;
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Point realSize = new Point();
+ // display.getRealSize(realSize); // this method was added at JELLY_BEAN_MR1
+ // int[] location = new int[2];
+ // rootView.getLocationOnScreen(location);
+ // bottomPadding = realSize.y - (location[1] + rootView.getHeight());
+ // }
+ // int heightDiff = rootView.getHeight() - (r.bottom - r.top);
+ // String param = "" ;
+ // if (heightDiff > 0 && (heightDiff + bottomPadding) > (h + bottomPadding) / 3) { // assume that this means that the keyboard is on
+ // param = "true";
+ // } else {
+ // param = "false";
+ // }
+
+ int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
+ if (IsInitialized()) {
+ MyUnitySendMessage(gameObject, "SetKeyboardVisible", Integer.toString(heightDiff));
+ }
+ }
+ };
+ activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+
+ public void Destroy() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ final WebView webView = mWebView;
+ mWebView = null;
+ if (webView == null) {
+ return;
+ }
+ if (mGlobalLayoutListener != null) {
+ View activityRootView = a.getWindow().getDecorView().getRootView();
+ activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
+ mGlobalLayoutListener = null;
+ }
+ webView.stopLoading();
+ if (mVideoView != null) {
+ layout.removeView(mVideoView);
+ layout.setBackgroundColor(0x00000000);
+ mVideoView = null;
+ }
+ layout.removeView(webView);
+ webView.destroy();
+ }});
+ }
+
+ public boolean SetURLPattern(final String allowPattern, final String denyPattern, final String hookPattern)
+ {
+ try {
+ final Pattern allow = (allowPattern == null || allowPattern.length() == 0) ? null : Pattern.compile(allowPattern);
+ final Pattern deny = (denyPattern == null || denyPattern.length() == 0) ? null : Pattern.compile(denyPattern);
+ final Pattern hook = (hookPattern == null || hookPattern.length() == 0) ? null : Pattern.compile(hookPattern);
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return false;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowRegex = allow;
+ mDenyRegex = deny;
+ mHookRegex = hook;
+ }});
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public void LoadURL(final String url) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (mCustomHeaders != null && !mCustomHeaders.isEmpty()) {
+ mWebView.loadUrl(url, mCustomHeaders);
+ } else {
+ mWebView.loadUrl(url);;
+ }
+ }});
+ }
+
+ public void LoadHTML(final String html, final String baseURL)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.loadDataWithBaseURL(baseURL, html, "text/html", "UTF8", null);
+ }});
+ }
+
+ public void EvaluateJS(final String js) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ mWebView.evaluateJavascript(js, null);
+ } else {
+ mWebView.loadUrl("javascript:" + URLEncoder.encode(js));
+ }
+ }});
+ }
+
+ public void GoBack() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.goBack();
+ }});
+ }
+
+ public void GoForward() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.goForward();
+ }});
+ }
+
+ public void Reload() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.reload();
+ }});
+ }
+
+ public void SetMargins(int left, int top, int right, int bottom) {
+ final FrameLayout.LayoutParams params
+ = new FrameLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT,
+ Gravity.NO_GRAVITY);
+ params.setMargins(left, top, right, bottom);
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setLayoutParams(params);
+ }});
+ }
+
+ public void SetVisibility(final boolean visibility) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (visibility) {
+ mWebView.setVisibility(View.VISIBLE);
+ layout.requestFocus();
+ mWebView.requestFocus();
+ if (layout != null && layout.getParent() != null && layout.getParent().getParent() != null) {
+ ((ViewGroup)layout.getParent().getParent()).requestLayout();
+ }
+ if (forceBringToFront && layout != null) {
+ layout.bringToFront();
+ }
+ } else {
+ mWebView.setVisibility(View.GONE);
+ }
+ }});
+ }
+
+ public void SetInteractionEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mInteractionEnabled = enabled;
+ }});
+ }
+
+ public void SetGoogleAppRedirectionEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mGoogleAppRedirectionEnabled = enabled;
+ }});
+ }
+
+ public void SetScrollbarsVisibility(final boolean visibility) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setHorizontalScrollBarEnabled(visibility);
+ mWebView.setVerticalScrollBarEnabled(visibility);
+ }});
+ }
+
+ public void SetAlertDialogEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAlertDialogEnabled = enabled;
+ }});
+ }
+
+ public void SetCameraAccess(final boolean allowed) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowVideoCapture = allowed;
+ }});
+ }
+
+ public void SetMicrophoneAccess(final boolean allowed) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowAudioCapture = allowed;
+ }});
+ }
+
+ public void SetNetworkAvailable(final boolean networkUp) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setNetworkAvailable(networkUp);
+ }});
+ }
+
+ // as the following explicitly pause/resume, pauseTimers()/resumeTimers() are always
+ // called. this differs from OnApplicationPause().
+ public void Pause() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.onPause();
+ mWebView.pauseTimers();
+ }});
+ }
+
+ public void Resume() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.onResume();
+ mWebView.resumeTimers();
+ }});
+ }
+
+ // cf. https://stackoverflow.com/questions/31788748/webview-youtube-videos-playing-in-background-on-rotation-and-minimise/31789193#31789193
+ public void OnApplicationPause(final boolean paused) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (paused) {
+ mWebView.onPause();
+ if (mWebView.getVisibility() == View.VISIBLE) {
+ // cf. https://qiita.com/nbhd/items/d31711faa8852143f3a4
+ mWebView.pauseTimers();
+ }
+ } else {
+ mWebView.onResume();
+ mWebView.resumeTimers();
+ if (forceBringToFront && layout != null) {
+ layout.bringToFront();
+ }
+ }
+ }});
+ }
+
+ public void AddCustomHeader(final String headerKey, final String headerValue)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ mCustomHeaders.put(headerKey, headerValue);
+ }});
+ }
+
+ public String GetCustomHeaderValue(final String headerKey)
+ {
+ if (mCustomHeaders == null) {
+ return null;
+ }
+ if (!mCustomHeaders.containsKey(headerKey)) {
+ return null;
+ }
+ return mCustomHeaders.get(headerKey);
+ }
+
+ public void RemoveCustomHeader(final String headerKey)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ if (mCustomHeaders.containsKey(headerKey)) {
+ mCustomHeaders.remove(headerKey);
+ }
+ }});
+ }
+
+ public void ClearCustomHeader()
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ mCustomHeaders.clear();
+ }});
+ }
+
+ public void ClearCookie(String url, String name)
+ {
+ try {
+ URL u = new URL(url);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ CookieManager cookieManager = CookieManager.getInstance();
+ String cookieString = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + u.getPath();
+ cookieManager.setCookie(url, cookieString);
+ cookieManager.flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ String cookieString = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + u.getHost() + "; path=" + u.getPath();
+ cookieManager.setCookie(url, cookieString);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ public void ClearCookies()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager.getInstance().removeAllCookies(null);
+ CookieManager.getInstance().flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ cookieManager.removeAllCookie();
+ cookieManager.removeSessionCookie();
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void SaveCookies()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager.getInstance().flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void GetCookies(String url)
+ {
+ CookieManager cookieManager = CookieManager.getInstance();
+ mWebViewPlugin.call("CallOnCookies", cookieManager.getCookie(url));
+ }
+
+ public void SetCookies(String url, List setCookieHeaders)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager cookieManager = CookieManager.getInstance();
+ for (String header : setCookieHeaders)
+ {
+ cookieManager.setCookie(url, header);
+ }
+ cookieManager.flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ for (String header : setCookieHeaders)
+ {
+ cookieManager.setCookie(url, header);
+ }
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void SetBasicAuthInfo(final String userName, final String password)
+ {
+ mBasicAuthUserName = userName;
+ mBasicAuthPassword = password;
+ }
+
+ public void ClearCache(final boolean includeDiskFiles)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.clearCache(includeDiskFiles);
+ }});
+ }
+
+ public void SetTextZoom(final int textZoom)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.getSettings().setTextZoom(textZoom);
+ }});
+ }
+
+ public void SetMixedContentMode(final int mode)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mWebView.getSettings().setMixedContentMode(mode);
+ }
+ }});
+ }
+}
diff --git a/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/RoundedWebView.java b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/RoundedWebView.java
new file mode 100644
index 00000000..a4f63177
--- /dev/null
+++ b/plugins/Android/webview-nofragment/src/main/java/net/gree/unitywebview/RoundedWebView.java
@@ -0,0 +1,91 @@
+package net.gree.unitywebview;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.webkit.WebView;
+
+public class RoundedWebView extends WebView{
+ private Context context;
+ private int width;
+ private int height;
+ private int radius;
+ private int dpRadius;
+
+ public RoundedWebView(Context context, int radius)
+ {
+ super(context);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ public RoundedWebView(Context context, AttributeSet attrs, int radius)
+ {
+ super(context, attrs);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ public RoundedWebView(Context context, AttributeSet attrs, int defStyleAttr, int radius)
+ {
+ super(context, attrs, defStyleAttr);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ private void initialize(Context context)
+ {
+ this.context = context;
+ }
+
+ private float dpToPx(Context context, int dp)
+ {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
+ }
+
+ @Override protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)
+ {
+ super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+
+ width = newWidth;
+
+ height = newHeight;
+
+ radius = (int)dpToPx(context, this.dpRadius);
+ }
+
+ @Override protected void onDraw(Canvas canvas)
+ {
+ super.onDraw(canvas);
+
+ Path path = new Path();
+
+ path.setFillType(Path.FillType.INVERSE_WINDING);
+
+ path.addRoundRect(new RectF(0, getScrollY(), width, getScrollY() + height), radius, radius, Path.Direction.CW);
+
+ canvas.drawPath(path, createPorterDuffClearPaint());
+ }
+
+ private Paint createPorterDuffClearPaint()
+ {
+ Paint paint = new Paint();
+
+ paint.setColor(Color.TRANSPARENT);
+
+ paint.setStyle(Paint.Style.FILL);
+
+ paint.setAntiAlias(true);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ return paint;
+ }
+}
diff --git a/plugins/Android/webview/build.gradle b/plugins/Android/webview/build.gradle
new file mode 100644
index 00000000..2add17a9
--- /dev/null
+++ b/plugins/Android/webview/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compileOnly fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.core:core:1.6.0'
+}
diff --git a/plugins/Android/webview/consumer-rules.pro b/plugins/Android/webview/consumer-rules.pro
new file mode 100644
index 00000000..8a85ff47
--- /dev/null
+++ b/plugins/Android/webview/consumer-rules.pro
@@ -0,0 +1,3 @@
+-keep class net.gree.unitywebview.** {
+ *;
+}
diff --git a/plugins/Android/webview/libs-ext/core-1.6.0.aar b/plugins/Android/webview/libs-ext/core-1.6.0.aar
new file mode 100644
index 00000000..cdf16635
Binary files /dev/null and b/plugins/Android/webview/libs-ext/core-1.6.0.aar differ
diff --git a/plugins/Android/webview/libs/.gitkeep b/plugins/Android/webview/libs/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/Android/webview/proguard-rules.pro b/plugins/Android/webview/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/plugins/Android/webview/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/plugins/Android/webview/src/main/AndroidManifest.xml b/plugins/Android/webview/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..9fea2cdf
--- /dev/null
+++ b/plugins/Android/webview/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayer.java b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayer.java
new file mode 100644
index 00000000..c3f8e613
--- /dev/null
+++ b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayer.java
@@ -0,0 +1,21 @@
+package net.gree.unitywebview;
+
+import com.unity3d.player.*;
+import android.content.ContextWrapper;
+import android.view.SurfaceView;
+import android.view.View;
+
+public class CUnityPlayer
+ extends UnityPlayer
+{
+ public CUnityPlayer(ContextWrapper contextwrapper) {
+ super(contextwrapper);
+ }
+
+ public void addView(View child) {
+ if (child instanceof SurfaceView) {
+ ((SurfaceView)child).setZOrderOnTop(false);
+ }
+ super.addView(child);
+ }
+}
diff --git a/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayerActivity.java b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayerActivity.java
new file mode 100644
index 00000000..308d8a13
--- /dev/null
+++ b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CUnityPlayerActivity.java
@@ -0,0 +1,18 @@
+package net.gree.unitywebview;
+
+import com.unity3d.player.*;
+import android.os.Bundle;
+
+public class CUnityPlayerActivity
+ extends UnityPlayerActivity
+{
+ @Override
+ public void onCreate(Bundle bundle) {
+ requestWindowFeature(1);
+ super.onCreate(bundle);
+ getWindow().setFormat(2);
+ mUnityPlayer = new CUnityPlayer(this);
+ setContentView(mUnityPlayer);
+ mUnityPlayer.requestFocus();
+ }
+}
diff --git a/plugins/Android/webview/src/main/java/net/gree/unitywebview/CWebViewPlugin.java b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
new file mode 100644
index 00000000..cd6ba33d
--- /dev/null
+++ b/plugins/Android/webview/src/main/java/net/gree/unitywebview/CWebViewPlugin.java
@@ -0,0 +1,1550 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+package net.gree.unitywebview;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.ContentValues;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+//#if UNITYWEBVIEW_DEVELOPMENT
+import android.content.pm.ApplicationInfo;
+//#endif
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Base64;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.webkit.GeolocationPermissions.Callback;
+import android.webkit.HttpAuthHandler;
+import android.webkit.JavascriptInterface;
+import android.webkit.JsResult;
+import android.webkit.JsPromptResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.widget.FrameLayout;
+import android.webkit.PermissionRequest;
+import android.webkit.ValueCallback;
+import androidx.core.content.FileProvider;
+// import android.support.v4.app.ActivityCompat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.unity3d.player.UnityPlayer;
+
+class CWebViewPluginInterface {
+ private CWebViewPlugin mPlugin;
+ private String mGameObject;
+
+ public CWebViewPluginInterface(CWebViewPlugin plugin, String gameObject) {
+ mPlugin = plugin;
+ mGameObject = gameObject;
+ }
+
+ @JavascriptInterface
+ public void call(final String message) {
+ call("CallFromJS", message);
+ }
+
+ public void call(final String method, final String message) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mPlugin.IsInitialized()) {
+ mPlugin.MyUnitySendMessage(mGameObject, method, message);
+ }
+ }});
+ }
+
+ @JavascriptInterface
+ public void saveDataURL(final String fileName, final String dataURL) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mPlugin.IsInitialized()) {
+ mPlugin.SaveDataURL(fileName, dataURL);
+ }
+ }});
+ }
+}
+
+public class CWebViewPlugin extends Fragment {
+ private static boolean forceBringToFront;
+ private static FrameLayout layout = null;
+ private Queue mMessages = new ArrayDeque();
+ private WebView mWebView;
+ private View mVideoView;
+ private OnGlobalLayoutListener mGlobalLayoutListener;
+ private CWebViewPluginInterface mWebViewPlugin;
+ private int progress;
+ private boolean canGoBack;
+ private boolean canGoForward;
+ private boolean mInteractionEnabled = true;
+ private boolean mGoogleAppRedirectionEnabled;
+ private boolean mAlertDialogEnabled;
+ private boolean mAllowVideoCapture;
+ private boolean mAllowAudioCapture;
+ private Hashtable mCustomHeaders;
+ private String mWebViewUA;
+ private Pattern mAllowRegex;
+ private Pattern mDenyRegex;
+ private Pattern mHookRegex;
+
+ private static final int INPUT_FILE_REQUEST_CODE = 1;
+ // private String mBase64Data;
+ // private static final int OUTPUT_FILE_REQUEST_CODE = 2;
+ private ValueCallback mUploadMessage;
+ private ValueCallback mFilePathCallback;
+ private Uri mCameraPhotoUri;
+
+ private static long instanceCount;
+ private long mInstanceId;
+ private boolean mPaused;
+ private List> mTransactions;
+
+ private String mBasicAuthUserName;
+ private String mBasicAuthPassword;
+
+ // cf. https://chromium.googlesource.com/chromium/src/+/3e5a94daf32200d65dea6072dd4d1b9a2025508b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java#121
+ private static final int ALLOWED_INTENT_FLAGS
+ = Intent.FLAG_EXCLUDE_STOPPED_PACKAGES
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS
+ | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+
+ // cf. https://chromium.googlesource.com/chromium/src/+/3e5a94daf32200d65dea6072dd4d1b9a2025508b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java#1808
+ private static void sanitizeQueryIntentActivitiesIntent(Intent intent) {
+ intent.setFlags(intent.getFlags() & ALLOWED_INTENT_FLAGS);
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.setComponent(null);
+
+ // Intent Selectors allow intents to bypass the intent filter and potentially send apps URIs
+ // they were not expecting to handle. https://crbug.com/1254422
+ intent.setSelector(null);
+ }
+
+ public void SaveDataURL(final String fileName, final String dataURL) {
+ if (!dataURL.startsWith("data:")) {
+ return;
+ }
+ String tmp = dataURL.substring("data:".length());
+ int i = tmp.indexOf(";");
+ if (i < 0) {
+ return;
+ }
+ final String base64data = tmp.substring(i + 1 + "base64,".length());
+ final String type = tmp.substring(0, i);
+ final Activity a = UnityPlayer.currentActivity;
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ values.put(MediaStore.MediaColumns.MIME_TYPE, type);
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
+ values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+ ContentResolver resolver = a.getContentResolver();
+ Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
+ if (uri != null) {
+ byte[] bytes = Base64.decode(base64data, Base64.DEFAULT);
+ try (OutputStream out = resolver.openOutputStream(uri)) {
+ if (out != null) {
+ out.write(bytes);
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ values.clear();
+ values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+ resolver.update(uri, values, null, null);
+ } else {
+ File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
+ String parent = file.getParent();
+ String name = file.getName();
+ String ext = "";
+ int i = name.lastIndexOf(".");
+ if (i >= 0) {
+ ext = name.substring(i);
+ name = name.substring(0, i);
+ }
+ for (i = 1; file.exists(); i++) {
+ file = new File(file.getParent(), name + " (" + i + ")" + ext);
+ }
+ try (FileOutputStream out = new FileOutputStream(file)) {
+ byte[] bytes = Base64.decode(base64data, Base64.DEFAULT);
+ if (out != null) {
+ out.write(bytes);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }});
+ }
+
+ // public void SaveDataURL(final String fileName, final String dataURL) {
+ // if (mBase64Data != null) {
+ // return;
+ // }
+ // if (!dataURL.startsWith("data:")) {
+ // return;
+ // }
+ // String tmp = dataURL.substring("data:".length());
+ // int i = tmp.indexOf(";");
+ // if (i < 0) {
+ // return;
+ // }
+ // mBase64Data = tmp.substring(i + 1 + "base64,".length());
+ // final String type = tmp.substring(0, i);
+ // Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ // intent.addCategory(Intent.CATEGORY_OPENABLE);
+ // intent.setType(type);
+ // intent.putExtra(Intent.EXTRA_TITLE, fileName);
+ // startActivityForResult(intent, OUTPUT_FILE_REQUEST_CODE);
+ // }
+
+ // cf. https://github.com/gree/unity-webview/issues/753
+ // cf. https://github.com/mixpanel/mixpanel-android/issues/400
+ // cf. https://github.com/mixpanel/mixpanel-android/commit/98bb530f9263f3bac0737971acc00dfef7ea4c35
+ public static boolean isDestroyed(final Activity a) {
+ if (a == null) {
+ return true;
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return a.isDestroyed();
+ } else {
+ return false;
+ }
+ }
+
+ public CWebViewPlugin() {
+ }
+
+ public void OnRequestFileChooserPermissionsResult(final boolean granted) {
+ final Activity a = UnityPlayer.currentActivity;
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (granted) {
+ ProcessChooser();
+ } else {
+ mFilePathCallback.onReceiveValue(null);
+ mFilePathCallback = null;
+ }
+ }});
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // if (requestCode == OUTPUT_FILE_REQUEST_CODE) {
+ // String base64data = mBase64Data;
+ // mBase64Data = null;
+ // // Check that the response is a good one
+ // if (resultCode == Activity.RESULT_OK) {
+ // final Activity a = UnityPlayer.currentActivity;
+ // final Uri uri = data.getData();
+ // final byte[] bytes = Base64.decode(base64data, Base64.DEFAULT);
+ // try (OutputStream out = getActivity().getContentResolver().openOutputStream(uri)) {
+ // if (out != null) {
+ // out.write(bytes);
+ // }
+ // } catch(Exception e) {
+ // e.printStackTrace();
+ // }
+ // }
+ // return;
+ // }
+ if (requestCode != INPUT_FILE_REQUEST_CODE) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (mFilePathCallback == null) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+ Uri[] results = null;
+ // Check that the response is a good one
+ if (resultCode == Activity.RESULT_OK) {
+ if (data == null) {
+ if (mCameraPhotoUri != null) {
+ results = new Uri[] { mCameraPhotoUri };
+ }
+ } else {
+ ClipData clipData = data.getClipData();
+ if (clipData != null) {
+ results = new Uri[clipData.getItemCount()];
+ for (int i = 0; i < clipData.getItemCount(); i++) {
+ results[i] = clipData.getItemAt(i).getUri();
+ }
+ } else {
+ String dataString = data.getDataString();
+ // cf. https://www.petitmonte.com/java/android_webview_camera.html
+ if (dataString == null) {
+ if (mCameraPhotoUri != null) {
+ results = new Uri[] { mCameraPhotoUri };
+ }
+ } else {
+ results = new Uri[] { Uri.parse(dataString) };
+ }
+ }
+ }
+ }
+ mFilePathCallback.onReceiveValue(results);
+ mFilePathCallback = null;
+ } else {
+ if (mUploadMessage == null) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+ Uri result = null;
+ if (resultCode == Activity.RESULT_OK) {
+ if (data != null) {
+ result = data.getData();
+ }
+ }
+ mUploadMessage.onReceiveValue(result);
+ mUploadMessage = null;
+ }
+ }
+
+ public static boolean IsWebViewAvailable() {
+ final Activity a = UnityPlayer.currentActivity;
+ FutureTask t = new FutureTask(new Callable() {
+ public Boolean call() throws Exception {
+ boolean isAvailable = false;
+ try {
+ WebView webView = new WebView(a);
+ if (webView != null) {
+ webView = null;
+ isAvailable = true;
+ }
+ } catch (Exception e) {
+ }
+ return isAvailable;
+ }
+ });
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return false;
+ }
+ a.runOnUiThread(t);
+ try {
+ return t.get();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public String GetMessage() {
+ synchronized(mMessages) {
+ return (mMessages.size() > 0) ? mMessages.poll() : null;
+ }
+ }
+
+ public void MyUnitySendMessage(String gameObject, String method, String message) {
+ synchronized(mMessages) {
+ mMessages.add(method + ":" + message);
+ }
+ }
+
+ public boolean IsInitialized() {
+ return mWebView != null;
+ }
+
+ public void Init(final String gameObject, final boolean transparent, final boolean zoom, final int androidForceDarkMode, final String ua, final int radius) {
+ final CWebViewPlugin self = this;
+ final Activity a = UnityPlayer.currentActivity;
+ instanceCount++;
+ mInstanceId = instanceCount;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView != null) {
+ return;
+ }
+
+ setRetainInstance(true);
+ if (mPaused) {
+ if (mTransactions == null) {
+ mTransactions = new ArrayList>();
+ }
+ mTransactions.add(Pair.create("add", self));
+ } else {
+ a
+ .getFragmentManager()
+ .beginTransaction()
+ .add(0, self, "CWebViewPlugin" + mInstanceId)
+ .commitAllowingStateLoss();
+ }
+
+ mAlertDialogEnabled = true;
+ mAllowVideoCapture = false;
+ mAllowAudioCapture = false;
+ mCustomHeaders = new Hashtable();
+
+ final WebView webView = (radius > 0) ? new RoundedWebView(a, radius) : new WebView(a);
+//#if UNITYWEBVIEW_DEVELOPMENT
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try {
+ ApplicationInfo ai = a.getPackageManager().getApplicationInfo(a.getPackageName(), 0);
+ if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ webView.setWebContentsDebuggingEnabled(true);
+ }
+ } catch (Exception ex) {
+ }
+ }
+//#endif
+ webView.setVisibility(View.GONE);
+ webView.setFocusable(true);
+ webView.setFocusableInTouchMode(true);
+
+ // webView.setWebChromeClient(new WebChromeClient() {
+ // public boolean onConsoleMessage(android.webkit.ConsoleMessage cm) {
+ // Log.d("Webview", cm.message());
+ // return true;
+ // }
+ // });
+ webView.setWebChromeClient(new WebChromeClient() {
+ // cf. https://stackoverflow.com/questions/40659198/how-to-access-the-camera-from-within-a-webview/47525818#47525818
+ // cf. https://github.com/googlesamples/android-PermissionRequest/blob/eff1d21f0b9c91d67c7f2a2303b591447e61e942/Application/src/main/java/com/example/android/permissionrequest/PermissionRequestFragment.java#L148-L161
+ @Override
+ public void onPermissionRequest(final PermissionRequest request) {
+ final String[] requestedResources = request.getResources();
+ for (String r : requestedResources) {
+ if ((r.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE) && mAllowVideoCapture)
+ || (r.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE) && mAllowAudioCapture)
+ || r.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
+ request.grant(requestedResources);
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // a.runOnUiThread(new Runnable() {public void run() {
+ // final String[] permissions = {
+ // "android.permission.CAMERA",
+ // "android.permission.RECORD_AUDIO",
+ // };
+ // ActivityCompat.requestPermissions(a, permissions, 0);
+ // }});
+ // }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ progress = newProgress;
+ }
+
+ @Override
+ public void onShowCustomView(View view, CustomViewCallback callback) {
+ super.onShowCustomView(view, callback);
+ if (layout != null) {
+ mVideoView = view;
+ layout.setBackgroundColor(0xff000000);
+ layout.addView(mVideoView);
+ }
+ }
+
+ @Override
+ public void onHideCustomView() {
+ super.onHideCustomView();
+ if (layout != null) {
+ layout.removeView(mVideoView);
+ layout.setBackgroundColor(0x00000000);
+ mVideoView = null;
+ }
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsAlert(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsConfirm(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ if (!mAlertDialogEnabled) {
+ result.cancel();
+ return true;
+ }
+ return super.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+ callback.invoke(origin, true, false);
+ }
+
+ // For Android < 3.0 (won't work because we cannot utilize FragmentActivity)
+ // public void openFileChooser(ValueCallback uploadFile) {
+ // openFileChooser(uploadFile, "");
+ // }
+
+ // For 3.0 <= Android < 4.1
+ public void openFileChooser(ValueCallback uploadFile, String acceptType) {
+ openFileChooser(uploadFile, acceptType, "");
+ }
+
+ // For 4.1 <= Android < 5.0
+ public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) {
+ if (mUploadMessage != null) {
+ mUploadMessage.onReceiveValue(null);
+ }
+ mUploadMessage = uploadFile;
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+ startActivityForResult(intent, INPUT_FILE_REQUEST_CODE);
+ }
+
+ // For Android 5.0+
+ @Override
+ public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {
+ // cf. https://github.com/googlearchive/chromium-webview-samples/blob/master/input-file-example/app/src/main/java/inputfilesample/android/chrome/google/com/inputfilesample/MainFragment.java
+
+ mFilePathCallback = filePathCallback;
+ MyUnitySendMessage(gameObject, "RequestFileChooserPermissions", "");
+ return true;
+ }
+
+ });
+
+ mWebViewPlugin = new CWebViewPluginInterface(self, gameObject);
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ webView.loadUrl("about:blank");
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnError", errorCode + "\t" + description + "\t" + failingUrl);
+ }
+
+ @Override
+ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnHttpError", Integer.toString(errorResponse.getStatusCode()));
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnStarted", url);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ mWebViewPlugin.call("CallOnLoaded", url);
+ }
+
+ @Override
+ public void onLoadResource(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+ if (mBasicAuthUserName != null && mBasicAuthPassword != null) {
+ handler.proceed(mBasicAuthUserName, mBasicAuthPassword);
+ } else {
+ handler.cancel();
+ }
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, final String url) {
+ if (mCustomHeaders == null || mCustomHeaders.isEmpty()) {
+ return super.shouldInterceptRequest(view, url);
+ }
+ return shouldInterceptRequest(view, url, null);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+ if (mCustomHeaders == null || mCustomHeaders.isEmpty()) {
+ return super.shouldInterceptRequest(view, request);
+ }
+ return shouldInterceptRequest(view, request.getUrl().toString(), request.getRequestHeaders());
+ }
+
+ public WebResourceResponse shouldInterceptRequest(WebView view, final String url, Map headers) {
+ try {
+ HttpURLConnection urlCon = (HttpURLConnection) (new URL(url)).openConnection();
+ urlCon.setInstanceFollowRedirects(false);
+ // The following should make HttpURLConnection have a same user-agent of webView)
+ // cf. http://d.hatena.ne.jp/faw/20070903/1188796959 (in Japanese)
+ urlCon.setRequestProperty("User-Agent", mWebViewUA);
+
+ if (mBasicAuthUserName != null && mBasicAuthPassword != null) {
+ String authorization = mBasicAuthUserName + ":" + mBasicAuthPassword;
+ urlCon.setRequestProperty("Authorization", "Basic " + Base64.encodeToString(authorization.getBytes(), Base64.NO_WRAP));
+ }
+
+ if (Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT_WATCH) {
+ // cf. https://issuetracker.google.com/issues/36989494
+ String cookies = CookieManager.getInstance().getCookie(url);
+ if (cookies != null && !cookies.isEmpty()) {
+ urlCon.addRequestProperty("Cookie", cookies);
+ }
+ }
+
+ if (headers != null) {
+ for (Map.Entry entry: headers.entrySet()) {
+ urlCon.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+ }
+ for (HashMap.Entry entry: mCustomHeaders.entrySet()) {
+ urlCon.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+
+ urlCon.connect();
+
+ int responseCode = urlCon.getResponseCode();
+ if (responseCode >= 300 && responseCode < 400) {
+ // To avoid a problem due to a mismatch between requested URL and returned content,
+ // make WebView request again in the case that redirection response was returned.
+ return null;
+ }
+
+ final List setCookieHeaders = urlCon.getHeaderFields().get("Set-Cookie");
+ if (setCookieHeaders != null) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT || Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT_WATCH) {
+ // In addition to getCookie, setCookie cause deadlock on Android 4.4.4 cf. https://issuetracker.google.com/issues/36989494
+ final Activity a = UnityPlayer.currentActivity;
+ if (!CWebViewPlugin.isDestroyed(a)) {
+ a.runOnUiThread(new Runnable() {
+ public void run() {
+ SetCookies(url, setCookieHeaders);
+ }
+ });
+ }
+ } else {
+ SetCookies(url, setCookieHeaders);
+ }
+ }
+
+ return new WebResourceResponse(
+ urlCon.getContentType().split(";", 2)[0],
+ urlCon.getContentEncoding(),
+ urlCon.getInputStream()
+ );
+
+ } catch (Exception e) {
+ return super.shouldInterceptRequest(view, url);
+ }
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ canGoBack = webView.canGoBack();
+ canGoForward = webView.canGoForward();
+ boolean pass = true;
+ if (mAllowRegex != null && mAllowRegex.matcher(url).find()) {
+ pass = true;
+ } else if (mDenyRegex != null && mDenyRegex.matcher(url).find()) {
+ pass = false;
+ }
+ if (!pass) {
+ return true;
+ }
+ if (url.startsWith("unity:")) {
+ String message = url.substring(6);
+ mWebViewPlugin.call("CallFromJS", message);
+ return true;
+ } else if (mHookRegex != null && mHookRegex.matcher(url).find()) {
+ mWebViewPlugin.call("CallOnHooked", url);
+ return true;
+ } else if (!mGoogleAppRedirectionEnabled && url.startsWith("https://www.google.com/")) {
+ mWebView.loadUrl(url);
+ return true;
+ } else if (!mGoogleAppRedirectionEnabled && url.startsWith("intent://www.google.com/")) {
+ return true;
+ } else if (!url.toLowerCase().endsWith(".pdf")
+ && !url.startsWith("https://maps.app.goo.gl")
+ && (url.startsWith("http://")
+ || url.startsWith("https://")
+ || url.startsWith("file://")
+ || url.startsWith("javascript:"))) {
+ mWebViewPlugin.call("CallOnStarted", url);
+ // Let webview handle the URL
+ return false;
+ } else if (url.startsWith("intent://") || url.startsWith("android-app://")) {
+ Intent intent = null;
+ try {
+ intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
+ // cf. https://www.m3tech.blog/entry/android-webview-intent-scheme
+ sanitizeQueryIntentActivitiesIntent(intent);
+ view.getContext().startActivity(intent);
+ } catch (URISyntaxException ex) {
+ } catch (ActivityNotFoundException ex) {
+ launchMarket(view.getContext(), intent);
+ }
+ return true;
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ // PackageManager pm = a.getPackageManager();
+ // List apps = pm.queryIntentActivities(intent, 0);
+ // if (apps.size() > 0) {
+ // view.getContext().startActivity(intent);
+ // }
+ try {
+ view.getContext().startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ }
+ return true;
+ }
+
+ private void launchMarket(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ String packageName = intent.getPackage();
+ if (packageName == null) {
+ return;
+ }
+ // cf. https://stackoverflow.com/questions/11753000/how-to-open-the-google-play-store-directly-from-my-android-application/11753070#11753070
+ try {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName));
+ context.startActivity(intent);
+ } catch (android.content.ActivityNotFoundException ex) {
+ try {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + packageName));
+ context.startActivity(intent);
+ } catch (android.content.ActivityNotFoundException ex2) {
+ }
+ }
+ }
+ });
+ webView.addJavascriptInterface(mWebViewPlugin , "Unity");
+
+ WebSettings webSettings = webView.getSettings();
+ if (ua != null && ua.length() > 0) {
+ webSettings.setUserAgentString(ua);
+ }
+ mWebViewUA = webSettings.getUserAgentString();
+ if (zoom) {
+ webSettings.setSupportZoom(true);
+ webSettings.setBuiltInZoomControls(true);
+ } else {
+ webSettings.setSupportZoom(false);
+ webSettings.setBuiltInZoomControls(false);
+ }
+ webSettings.setDisplayZoomControls(false);
+ webSettings.setLoadWithOverviewMode(true);
+ webSettings.setUseWideViewPort(true);
+ webSettings.setJavaScriptEnabled(true);
+ webSettings.setGeolocationEnabled(true);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // Log.i("CWebViewPlugin", "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
+ webSettings.setAllowUniversalAccessFromFileURLs(true);
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 17) {
+ webSettings.setMediaPlaybackRequiresUserGesture(false);
+ }
+ webSettings.setDatabaseEnabled(true);
+ webSettings.setDomStorageEnabled(true);
+ String databasePath = webView.getContext().getDir("databases", Context.MODE_PRIVATE).getPath();
+ webSettings.setDatabasePath(databasePath);
+ webSettings.setAllowFileAccess(true); // cf. https://github.com/gree/unity-webview/issues/625
+
+ // cf. https://forum.unity.com/threads/unity-ios-dark-mode.805344/#post-6476051
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ switch (androidForceDarkMode) {
+ case 0:
+ {
+ Configuration configuration = UnityPlayer.currentActivity.getResources().getConfiguration();
+ switch (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
+ case Configuration.UI_MODE_NIGHT_NO:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_OFF);
+ break;
+ case Configuration.UI_MODE_NIGHT_YES:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
+ break;
+ }
+ }
+ break;
+ case 1:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_OFF);
+ break;
+ case 2:
+ webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
+ break;
+ }
+ }
+
+ if (transparent) {
+ webView.setBackgroundColor(0x00000000);
+ }
+
+ // cf. https://stackoverflow.com/questions/3853794/disable-webview-touch-events-in-android/3856199#3856199
+ webView.setOnTouchListener(
+ new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ return !mInteractionEnabled;
+ }
+ });
+
+ if (layout == null || layout.getParent() != a.findViewById(android.R.id.content)) {
+ layout = new FrameLayout(a);
+ a.addContentView(
+ layout,
+ new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ layout.setFocusable(true);
+ layout.setFocusableInTouchMode(true);
+ }
+ layout.addView(
+ webView,
+ new FrameLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT,
+ Gravity.NO_GRAVITY));
+ mWebView = webView;
+ }});
+
+ final View activityRootView = a.getWindow().getDecorView().getRootView();
+ mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ android.graphics.Rect r = new android.graphics.Rect();
+ //r will be populated with the coordinates of your view that area still visible.
+ activityRootView.getWindowVisibleDisplayFrame(r);
+ android.view.Display display = a.getWindowManager().getDefaultDisplay();
+ // cf. http://stackoverflow.com/questions/9654016/getsize-giving-me-errors/10564149#10564149
+ int h = 0;
+ try {
+ Point size = new Point();
+ display.getSize(size);
+ h = size.y;
+ } catch (java.lang.NoSuchMethodError err) {
+ h = display.getHeight();
+ }
+
+ // View rootView = activityRootView.getRootView();
+ // int bottomPadding = 0;
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Point realSize = new Point();
+ // display.getRealSize(realSize); // this method was added at JELLY_BEAN_MR1
+ // int[] location = new int[2];
+ // rootView.getLocationOnScreen(location);
+ // bottomPadding = realSize.y - (location[1] + rootView.getHeight());
+ // }
+ // int heightDiff = rootView.getHeight() - (r.bottom - r.top);
+ // String param = "" ;
+ // if (heightDiff > 0 && (heightDiff + bottomPadding) > (h + bottomPadding) / 3) { // assume that this means that the keyboard is on
+ // param = "" + heightDiff;
+ // } else {
+ // param = "false";
+ // }
+
+ int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
+ if (IsInitialized()) {
+ MyUnitySendMessage(gameObject, "SetKeyboardVisible", Integer.toString(heightDiff));
+ }
+ }
+ };
+ activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+
+ private void ProcessChooser() {
+ mCameraPhotoUri = null;
+ Intent takePictureIntent = null;
+ if (mAllowVideoCapture) {
+ takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
+ // Create the File where the photo should go
+ File photoFile = null;
+ try {
+ photoFile = createImageFile();
+ } catch (IOException ex) {
+ // Error occurred while creating the File
+ //Log.e("CWebViewPlugin", "Unable to create Image File", ex);
+ }
+ // Continue only if the File was successfully created
+ if (photoFile != null) {
+ takePictureIntent.putExtra("PhotoPath", photoFile);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ mCameraPhotoUri = FileProvider.getUriForFile(getActivity(), getActivity().getPackageName() + ".unitywebview.fileprovider", photoFile);
+ } else {
+ mCameraPhotoUri = Uri.parse("file:" + photoFile.getAbsolutePath());
+ }
+ takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraPhotoUri);
+ //takePictureIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, "720000");
+ } else {
+ takePictureIntent = null;
+ }
+ }
+ }
+
+ Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+ contentSelectionIntent.setType("*/*");
+
+ Intent[] intentArray;
+ if(takePictureIntent != null) {
+ intentArray = new Intent[]{takePictureIntent};
+ } else {
+ intentArray = new Intent[0];
+ }
+
+ Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
+ chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
+ // chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
+
+ startActivityForResult(Intent.createChooser(chooserIntent, "Select images"), INPUT_FILE_REQUEST_CODE);
+ }
+
+ private File createImageFile() throws IOException {
+ // Create an image file name
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+ String imageFileName = "JPEG_" + timeStamp + "_";
+ File storageDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_DCIM);
+ if (!storageDir.exists()) {
+ storageDir.mkdirs();
+ }
+ File imageFile = File.createTempFile(imageFileName, /* prefix */
+ ".jpg", /* suffix */
+ storageDir /* directory */
+ );
+ return imageFile;
+ }
+
+ public void Destroy() {
+ final Activity a = UnityPlayer.currentActivity;
+ final CWebViewPlugin self = this;
+ mMessages.clear();
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ final WebView webView = mWebView;
+ mWebView = null;
+ if (webView == null) {
+ return;
+ }
+ if (mGlobalLayoutListener != null) {
+ View activityRootView = a.getWindow().getDecorView().getRootView();
+ activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
+ mGlobalLayoutListener = null;
+ }
+ webView.stopLoading();
+ if (mVideoView != null) {
+ layout.removeView(mVideoView);
+ layout.setBackgroundColor(0x00000000);
+ mVideoView = null;
+ }
+ layout.removeView(webView);
+ webView.destroy();
+
+ if (mPaused) {
+ if (mTransactions == null) {
+ mTransactions = new ArrayList>();
+ }
+ mTransactions.add(Pair.create("remove", self));
+ } else {
+ a
+ .getFragmentManager()
+ .beginTransaction()
+ .remove(self)
+ .commitAllowingStateLoss();
+ }
+
+ }});
+ }
+
+ public boolean SetURLPattern(final String allowPattern, final String denyPattern, final String hookPattern)
+ {
+ try {
+ final Pattern allow = (allowPattern == null || allowPattern.length() == 0) ? null : Pattern.compile(allowPattern);
+ final Pattern deny = (denyPattern == null || denyPattern.length() == 0) ? null : Pattern.compile(denyPattern);
+ final Pattern hook = (hookPattern == null || hookPattern.length() == 0) ? null : Pattern.compile(hookPattern);
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return false;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowRegex = allow;
+ mDenyRegex = deny;
+ mHookRegex = hook;
+ }});
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public void LoadURL(final String url) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (mCustomHeaders != null && !mCustomHeaders.isEmpty()) {
+ mWebView.loadUrl(url, mCustomHeaders);
+ } else {
+ mWebView.loadUrl(url);;
+ }
+ }});
+ }
+
+ public void LoadHTML(final String html, final String baseURL)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.loadDataWithBaseURL(baseURL, html, "text/html", "UTF8", null);
+ }});
+ }
+
+ public void EvaluateJS(final String js) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ mWebView.evaluateJavascript(js, null);
+ } else {
+ mWebView.loadUrl("javascript:" + URLEncoder.encode(js));
+ }
+ }});
+ }
+
+ public void GoBack() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.goBack();
+ }});
+ }
+
+ public void GoForward() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.goForward();
+ }});
+ }
+
+ public void Reload() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.reload();
+ }});
+ }
+
+ public void SetMargins(int left, int top, int right, int bottom) {
+ final FrameLayout.LayoutParams params
+ = new FrameLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT,
+ Gravity.NO_GRAVITY);
+ params.setMargins(left, top, right, bottom);
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setLayoutParams(params);
+ }});
+ }
+
+ public void SetVisibility(final boolean visibility) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (visibility) {
+ mWebView.setVisibility(View.VISIBLE);
+ layout.requestFocus();
+ mWebView.requestFocus();
+ if (layout != null && layout.getParent() != null && layout.getParent().getParent() != null) {
+ ((ViewGroup)layout.getParent().getParent()).requestLayout();
+ }
+ if (forceBringToFront && layout != null) {
+ layout.bringToFront();
+ }
+ } else {
+ mWebView.setVisibility(View.GONE);
+ }
+ }});
+ }
+
+ public void SetInteractionEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mInteractionEnabled = enabled;
+ }});
+ }
+
+ public void SetGoogleAppRedirectionEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mGoogleAppRedirectionEnabled = enabled;
+ }});
+ }
+
+ public void SetScrollbarsVisibility(final boolean visibility) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setHorizontalScrollBarEnabled(visibility);
+ mWebView.setVerticalScrollBarEnabled(visibility);
+ }});
+ }
+
+ public void SetAlertDialogEnabled(final boolean enabled) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAlertDialogEnabled = enabled;
+ }});
+ }
+
+ public void SetCameraAccess(final boolean allowed) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowVideoCapture = allowed;
+ }});
+ }
+
+ public void SetMicrophoneAccess(final boolean allowed) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ mAllowAudioCapture = allowed;
+ }});
+ }
+
+ public void SetNetworkAvailable(final boolean networkUp) {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.setNetworkAvailable(networkUp);
+ }});
+ }
+
+ // as the following explicitly pause/resume, pauseTimers()/resumeTimers() are always
+ // called. this differs from OnApplicationPause().
+ public void Pause() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.onPause();
+ mWebView.pauseTimers();
+ }});
+ }
+
+ public void Resume() {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.onResume();
+ mWebView.resumeTimers();
+ }});
+ }
+
+ // cf. https://stackoverflow.com/questions/31788748/webview-youtube-videos-playing-in-background-on-rotation-and-minimise/31789193#31789193
+ public void OnApplicationPause(boolean paused) {
+ mPaused = paused;
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (!mPaused) {
+ if (mTransactions != null) {
+ for (Pair pair : mTransactions) {
+ CWebViewPlugin self = pair.second;
+ switch (pair.first) {
+ case "add":
+ a
+ .getFragmentManager()
+ .beginTransaction()
+ .add(0, self, "CWebViewPlugin" + mInstanceId)
+ .commitAllowingStateLoss();
+ break;
+ case "remove":
+ a
+ .getFragmentManager()
+ .beginTransaction()
+ .remove(self)
+ .commitAllowingStateLoss();
+ break;
+ }
+ }
+ mTransactions.clear();
+ }
+ }
+ if (mWebView == null) {
+ return;
+ }
+ if (mPaused) {
+ mWebView.onPause();
+ if (mWebView.getVisibility() == View.VISIBLE) {
+ // cf. https://qiita.com/nbhd/items/d31711faa8852143f3a4
+ mWebView.pauseTimers();
+ }
+ } else {
+ mWebView.onResume();
+ mWebView.resumeTimers();
+ if (forceBringToFront && layout != null) {
+ layout.bringToFront();
+ }
+ }
+ }});
+ }
+
+ public void AddCustomHeader(final String headerKey, final String headerValue)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ mCustomHeaders.put(headerKey, headerValue);
+ }});
+ }
+
+ public String GetCustomHeaderValue(final String headerKey)
+ {
+ if (mCustomHeaders == null) {
+ return null;
+ }
+ if (!mCustomHeaders.containsKey(headerKey)) {
+ return null;
+ }
+ return mCustomHeaders.get(headerKey);
+ }
+
+ public void RemoveCustomHeader(final String headerKey)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ if (mCustomHeaders.containsKey(headerKey)) {
+ mCustomHeaders.remove(headerKey);
+ }
+ }});
+ }
+
+ public void ClearCustomHeader()
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mCustomHeaders == null) {
+ return;
+ }
+ mCustomHeaders.clear();
+ }});
+ }
+
+ public void ClearCookie(String url, String name)
+ {
+ try {
+ URL u = new URL(url);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ CookieManager cookieManager = CookieManager.getInstance();
+ String cookieString = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + u.getPath();
+ cookieManager.setCookie(url, cookieString);
+ cookieManager.flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ String cookieString = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + u.getHost() + "; path=" + u.getPath();
+ cookieManager.setCookie(url, cookieString);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ public void ClearCookies()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager.getInstance().removeAllCookies(null);
+ CookieManager.getInstance().flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ cookieManager.removeAllCookie();
+ cookieManager.removeSessionCookie();
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void SaveCookies()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager.getInstance().flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void GetCookies(String url)
+ {
+ CookieManager cookieManager = CookieManager.getInstance();
+ mWebViewPlugin.call("CallOnCookies", cookieManager.getCookie(url));
+ }
+
+ public void SetCookies(String url, List setCookieHeaders)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ CookieManager cookieManager = CookieManager.getInstance();
+ for (String header : setCookieHeaders)
+ {
+ cookieManager.setCookie(url, header);
+ }
+ cookieManager.flush();
+ } else {
+ final Activity a = UnityPlayer.currentActivity;
+ CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(a);
+ cookieSyncManager.startSync();
+ CookieManager cookieManager = CookieManager.getInstance();
+ for (String header : setCookieHeaders)
+ {
+ cookieManager.setCookie(url, header);
+ }
+ cookieSyncManager.stopSync();
+ cookieSyncManager.sync();
+ }
+ }
+
+ public void SetBasicAuthInfo(final String userName, final String password)
+ {
+ mBasicAuthUserName = userName;
+ mBasicAuthPassword = password;
+ }
+
+ public void ClearCache(final boolean includeDiskFiles)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.clearCache(includeDiskFiles);
+ }});
+ }
+
+ public void SetTextZoom(final int textZoom)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.getSettings().setTextZoom(textZoom);
+ }});
+ }
+
+ public void SetMixedContentMode(final int mode)
+ {
+ final Activity a = UnityPlayer.currentActivity;
+ if (CWebViewPlugin.isDestroyed(a)) {
+ return;
+ }
+ a.runOnUiThread(new Runnable() {public void run() {
+ if (mWebView == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mWebView.getSettings().setMixedContentMode(mode);
+ }
+ }});
+ }
+}
diff --git a/plugins/Android/webview/src/main/java/net/gree/unitywebview/RoundedWebView.java b/plugins/Android/webview/src/main/java/net/gree/unitywebview/RoundedWebView.java
new file mode 100644
index 00000000..a4f63177
--- /dev/null
+++ b/plugins/Android/webview/src/main/java/net/gree/unitywebview/RoundedWebView.java
@@ -0,0 +1,91 @@
+package net.gree.unitywebview;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.webkit.WebView;
+
+public class RoundedWebView extends WebView{
+ private Context context;
+ private int width;
+ private int height;
+ private int radius;
+ private int dpRadius;
+
+ public RoundedWebView(Context context, int radius)
+ {
+ super(context);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ public RoundedWebView(Context context, AttributeSet attrs, int radius)
+ {
+ super(context, attrs);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ public RoundedWebView(Context context, AttributeSet attrs, int defStyleAttr, int radius)
+ {
+ super(context, attrs, defStyleAttr);
+ this.dpRadius = radius;
+ initialize(context);
+ }
+
+ private void initialize(Context context)
+ {
+ this.context = context;
+ }
+
+ private float dpToPx(Context context, int dp)
+ {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
+ }
+
+ @Override protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)
+ {
+ super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+
+ width = newWidth;
+
+ height = newHeight;
+
+ radius = (int)dpToPx(context, this.dpRadius);
+ }
+
+ @Override protected void onDraw(Canvas canvas)
+ {
+ super.onDraw(canvas);
+
+ Path path = new Path();
+
+ path.setFillType(Path.FillType.INVERSE_WINDING);
+
+ path.addRoundRect(new RectF(0, getScrollY(), width, getScrollY() + height), radius, radius, Path.Direction.CW);
+
+ canvas.drawPath(path, createPorterDuffClearPaint());
+ }
+
+ private Paint createPorterDuffClearPaint()
+ {
+ Paint paint = new Paint();
+
+ paint.setColor(Color.TRANSPARENT);
+
+ paint.setStyle(Paint.Style.FILL);
+
+ paint.setAntiAlias(true);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ return paint;
+ }
+}
diff --git a/plugins/Editor/UnityWebViewPostprocessBuild.cs b/plugins/Editor/UnityWebViewPostprocessBuild.cs
index dd107ca1..bae62938 100644
--- a/plugins/Editor/UnityWebViewPostprocessBuild.cs
+++ b/plugins/Editor/UnityWebViewPostprocessBuild.cs
@@ -1,202 +1,568 @@
+#if UNITY_EDITOR
+using System.Collections.Generic;
using System.Collections;
using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
using System.Text;
using System.Xml;
+using System;
using UnityEditor.Android;
+#if UNITY_2020_1_OR_NEWER
+using UnityEditor.Build.Reporting;
+#endif
+#if UNITY_2018_1_OR_NEWER
+using UnityEditor.Build;
+#endif
using UnityEditor.Callbacks;
-using UnityEditor.iOS.Xcode;
using UnityEditor;
using UnityEngine;
-#if UNITY_2018_1_OR_NEWER
-public class UnityWebViewPostprocessBuild : IPostGenerateGradleAndroidProject
+namespace Gree.UnityWebView
+{
+#if UNITY_2020_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuildWithReport, IPostGenerateGradleAndroidProject
+#elif UNITY_2018_1_OR_NEWER
+ public class UnityWebViewPostprocessBuild : IPreprocessBuild, IPostGenerateGradleAndroidProject
#else
-public class UnityWebViewPostprocessBuild
+ public class UnityWebViewPostprocessBuild
#endif
-{
- //// for android/unity 2018.1 or newer
- //// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
- //// cf. https://github.com/Over17/UnityAndroidManifestCallback
+ {
+ private static bool nofragment = false;
+
+ //// for android/unity 2018.1 or newer
+ //// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
+ //// cf. https://github.com/Over17/UnityAndroidManifestCallback
#if UNITY_2018_1_OR_NEWER
- public void OnPostGenerateGradleAndroidProject(string basePath) {
- var changed = false;
- var androidManifest = new AndroidManifest(GetManifestPath(basePath));
- changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITY_2020_1_OR_NEWER
+ public void OnPreprocessBuild(BuildReport buildReport) {
+ var buildTarget = buildReport.summary.platform;
+#else
+ public void OnPreprocessBuild(BuildTarget buildTarget, string path) {
+#endif
+ if (buildTarget == BuildTarget.Android) {
+ var dev = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ var rel = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ if (!File.Exists(dev) || !File.Exists(rel)) {
+ dev = "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
+ rel = "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
+ }
+ var src = (EditorUserBuildSettings.development) ? dev : rel;
+ //Directory.CreateDirectory("Temp/StagingArea/aar");
+ //File.Copy(src, "Temp/StagingArea/aar/WebViewPlugin.aar", true);
+ Directory.CreateDirectory("Assets/Plugins/Android");
+ File.Copy(src, "Assets/Plugins/Android/WebViewPlugin.aar", true);
+ }
+ }
+
+ public void OnPostGenerateGradleAndroidProject(string basePath) {
+ var changed = false;
+ var androidManifest = new AndroidManifest(GetManifestPath(basePath));
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider(basePath) || changed);
+ {
+ var path = GetBuildGradlePath(basePath);
+ var lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n").Split(new[]{'\n'});
+ {
+ var lines = new List();
+ var independencies = false;
+ foreach (var line in lines0) {
+ if (line == "dependencies {") {
+ independencies = true;
+ } else if (independencies && line == "}") {
+ independencies = false;
+ lines.Add(" implementation 'androidx.core:core:1.6.0'");
+ } else if (independencies) {
+ if (line.Contains("implementation(name: 'core")
+ || line.Contains("implementation(name: 'androidx.core.core")
+ || line.Contains("implementation 'androidx.core:core")) {
+ break;
+ }
+ }
+ lines.Add(line);
+ }
+ if (lines.Count > lines0.Length) {
+ File.WriteAllText(path, string.Join("\n", lines) + "\n");
+ }
+ }
+ }
+ {
+ var path = GetGradlePropertiesPath(basePath);
+ var lines0 = "";
+ var lines = "";
+ if (File.Exists(path)) {
+ lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n") + "\n";
+ lines = lines0;
+ }
+ if (!lines.Contains("android.useAndroidX=true")) {
+ lines += "android.useAndroidX=true\n";
+ }
+ if (!lines.Contains("android.enableJetifier=true")) {
+ lines += "android.enableJetifier=true\n";
+ }
+ if (lines != lines0) {
+ File.WriteAllText(path, lines);
+ }
+ }
+ }
+ changed = (androidManifest.SetExported(true) || changed);
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
- changed = (androidManifest.AddCamera() || changed);
+ changed = (androidManifest.AddCamera() || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
- changed = (androidManifest.AddMicrophone() || changed);
+ changed = (androidManifest.AddMicrophone() || changed);
#endif
- if (changed) {
- androidManifest.Save();
- Debug.Log("unitywebview: adjusted AndroidManifest.xml.");
+ }
+ if (changed) {
+ androidManifest.Save();
+ Debug.Log("unitywebview: adjusted AndroidManifest.xml.");
+ }
}
- }
#endif
- public int callbackOrder {
- get {
- return 1;
+ public int callbackOrder {
+ get {
+ return 1;
+ }
}
- }
- private string GetManifestPath(string basePath) {
- var pathBuilder = new StringBuilder(basePath);
- pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
- pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
- pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
- return pathBuilder.ToString();
- }
+ private string GetManifestPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
+ return pathBuilder.ToString();
+ }
- //// for others
-
- [PostProcessBuild(100)]
- public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
-#if !UNITY_2018_1_OR_NEWER
- if (buildTarget == BuildTarget.Android) {
- string manifest = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
- if (!File.Exists(manifest)) {
- string manifest0 = Path.Combine(Application.dataPath, "../Temp/StagingArea/AndroidManifest-main.xml");
- if (!File.Exists(manifest0)) {
- Debug.LogError("unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again.");
- return;
+ private string GetBuildGradlePath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("build.gradle");
+ return pathBuilder.ToString();
+ }
+
+ private string GetGradlePropertiesPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ if (basePath.EndsWith("unityLibrary")) {
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("..");
+ }
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("gradle.properties");
+ return pathBuilder.ToString();
+ }
+
+ //// for others
+
+ [PostProcessBuild(100)]
+ public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
+#if UNITY_2018_1_OR_NEWER
+ try {
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar");
+ File.Delete("Assets/Plugins/Android/WebViewPlugin.aar.meta");
+ Directory.Delete("Assets/Plugins/Android");
+ File.Delete("Assets/Plugins/Android.meta");
+ Directory.Delete("Assets/Plugins");
+ File.Delete("Assets/Plugins.meta");
+ } catch (Exception) {
+ }
+#else
+ if (buildTarget == BuildTarget.Android) {
+ string manifest = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
+ if (!File.Exists(manifest)) {
+ string manifest0 = Path.Combine(Application.dataPath, "../Temp/StagingArea/AndroidManifest-main.xml");
+ if (!File.Exists(manifest0)) {
+ Debug.LogError("unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again.");
+ return;
+ } else {
+ File.Copy(manifest0, manifest, true);
+ }
+ }
+ var changed = false;
+ if (EditorUserBuildSettings.development) {
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
} else {
- File.Copy(manifest0, manifest);
+ if (!File.Exists("Assets/Plugins/Android/WebView.aar")
+ || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl"))) {
+ File.Copy("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
+ changed = true;
+ }
}
- }
- var changed = false;
- var androidManifest = new AndroidManifest(manifest);
- changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+ var androidManifest = new AndroidManifest(manifest);
+ if (!nofragment) {
+ changed = (androidManifest.AddFileProvider("Assets/Plugins/Android") || changed);
+ var files = Directory.GetFiles("Assets/Plugins/Android/");
+ var found = false;
+ foreach (var file in files) {
+ if (Regex.IsMatch(file, @"^Assets/Plugins/Android/(androidx\.core\.)?core-.*.aar$")) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ foreach (var file in files) {
+ var match = Regex.Match(file, @"^Assets/Plugins/Android/(core.*.aar).tmpl$");
+ if (match.Success) {
+ var name = match.Groups[1].Value;
+ File.Copy(file, "Assets/Plugins/Android/" + name, true);
+ break;
+ }
+ }
+ }
+ }
+ changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
+ changed = (androidManifest.SetHardwareAccelerated(true) || changed);
+#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
+ changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
+#endif
+ if (!nofragment) {
+ changed = (androidManifest.AddGallery() || changed);
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
- changed = (androidManifest.AddCamera() || changed);
+ changed = (androidManifest.AddCamera() || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
- changed = (androidManifest.AddMicrophone() || changed);
+ changed = (androidManifest.AddMicrophone() || changed);
#endif
+ }
#if UNITY_5_6_0 || UNITY_5_6_1
- changed = (androidManifest.SetActivityName("net.gree.unitywebview.CUnityPlayerActivity") || changed);
+ changed = (androidManifest.SetActivityName("net.gree.unitywebview.CUnityPlayerActivity") || changed);
#endif
- if (changed) {
- androidManifest.Save();
- Debug.LogError("unitywebview: adjusted AndroidManifest.xml. Please rebuild the app.");
+ if (changed) {
+ androidManifest.Save();
+ Debug.LogError("unitywebview: adjusted AndroidManifest.xml and/or WebView.aar. Please rebuild the app.");
+ }
}
- }
#endif
- if (buildTarget == BuildTarget.iOS) {
- string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
- PBXProject proj = new PBXProject();
- proj.ReadFromString(File.ReadAllText(projPath));
- string target = proj.TargetGuidByName("Unity-iPhone");
- proj.AddFrameworkToProject(target, "WebKit.framework", false);
- File.WriteAllText(projPath, proj.WriteToString());
+ if (buildTarget == BuildTarget.iOS) {
+ string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
+ var type = Type.GetType("UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode");
+ if (type == null)
+ {
+ Debug.LogError("unitywebview: failed to get PBXProject. please install iOS build support.");
+ return;
+ }
+ var src = File.ReadAllText(projPath);
+ //dynamic proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ var proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
+ //proj.ReadFromString(src);
+ {
+ var method = type.GetMethod("ReadFromString");
+ method.Invoke(proj, new object[]{src});
+ }
+ var target = "";
+#if UNITY_2019_3_OR_NEWER
+ //target = proj.GetUnityFrameworkTargetGuid();
+ {
+ var method = type.GetMethod("GetUnityFrameworkTargetGuid");
+ target = (string)method.Invoke(proj, null);
+ }
+#else
+ //target = proj.TargetGuidByName("Unity-iPhone");
+ {
+ var method = type.GetMethod("TargetGuidByName");
+ target = (string)method.Invoke(proj, new object[]{"Unity-iPhone"});
+ }
+#endif
+ //proj.AddFrameworkToProject(target, "WebKit.framework", false);
+ {
+ var method = type.GetMethod("AddFrameworkToProject");
+ method.Invoke(proj, new object[]{target, "WebKit.framework", false});
+ }
+ var cflags = "";
+ if (EditorUserBuildSettings.development) {
+ cflags += " -DUNITYWEBVIEW_DEVELOPMENT";
+ }
+#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
+ cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS";
+#endif
+ cflags = cflags.Trim();
+ if (!string.IsNullOrEmpty(cflags)) {
+ // proj.AddBuildProperty(target, "OTHER_LDFLAGS", cflags);
+ var method = type.GetMethod("AddBuildProperty", new Type[]{typeof(string), typeof(string), typeof(string)});
+ method.Invoke(proj, new object[]{target, "OTHER_CFLAGS", cflags});
+ }
+ var dst = "";
+ //dst = proj.WriteToString();
+ {
+ var method = type.GetMethod("WriteToString");
+ dst = (string)method.Invoke(proj, null);
+ }
+ File.WriteAllText(projPath, dst);
+ }
}
}
-}
-internal class AndroidXmlDocument : XmlDocument {
- private string m_Path;
- protected XmlNamespaceManager nsMgr;
- public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
+ internal class AndroidXmlDocument : XmlDocument {
+ private string m_Path;
+ protected XmlNamespaceManager nsMgr;
+ public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
- public AndroidXmlDocument(string path) {
- m_Path = path;
- using (var reader = new XmlTextReader(m_Path)) {
- reader.Read();
- Load(reader);
+ public AndroidXmlDocument(string path) {
+ m_Path = path;
+ using (var reader = new XmlTextReader(m_Path)) {
+ reader.Read();
+ Load(reader);
+ }
+ nsMgr = new XmlNamespaceManager(NameTable);
+ nsMgr.AddNamespace("android", AndroidXmlNamespace);
}
- nsMgr = new XmlNamespaceManager(NameTable);
- nsMgr.AddNamespace("android", AndroidXmlNamespace);
- }
- public string Save() {
- return SaveAs(m_Path);
- }
+ public string Save() {
+ return SaveAs(m_Path);
+ }
- public string SaveAs(string path) {
- using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) {
- writer.Formatting = Formatting.Indented;
- Save(writer);
+ public string SaveAs(string path) {
+ using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) {
+ writer.Formatting = Formatting.Indented;
+ Save(writer);
+ }
+ return path;
}
- return path;
}
-}
-internal class AndroidManifest : AndroidXmlDocument {
- private readonly XmlElement ManifestElement;
- private readonly XmlElement ApplicationElement;
+ internal class AndroidManifest : AndroidXmlDocument {
+ private readonly XmlElement ManifestElement;
+ private readonly XmlElement ApplicationElement;
- public AndroidManifest(string path) : base(path) {
- ManifestElement = SelectSingleNode("/manifest") as XmlElement;
- ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
- }
+ public AndroidManifest(string path) : base(path) {
+ ManifestElement = SelectSingleNode("/manifest") as XmlElement;
+ ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
+ }
- private XmlAttribute CreateAndroidAttribute(string key, string value) {
- XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
- attr.Value = value;
- return attr;
- }
+ private XmlAttribute CreateAndroidAttribute(string key, string value) {
+ XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
+ attr.Value = value;
+ return attr;
+ }
- internal XmlNode GetActivityWithLaunchIntent() {
- return
- SelectSingleNode(
- "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and "
- + "intent-filter/category/@android:name='android.intent.category.LAUNCHER']",
- nsMgr);
- }
+ internal XmlNode GetActivityWithLaunchIntent() {
+ return
+ SelectSingleNode(
+ "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and "
+ + "intent-filter/category/@android:name='android.intent.category.LAUNCHER']",
+ nsMgr);
+ }
- internal bool SetHardwareAccelerated(bool enabled) {
- bool changed = false;
- var activity = GetActivityWithLaunchIntent() as XmlElement;
- if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
- activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, (enabled) ? "true" : "false");
- changed = true;
+ internal bool SetUsesCleartextTraffic(bool enabled) {
+ // android:usesCleartextTraffic
+ bool changed = false;
+ if (ApplicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ ApplicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
}
- return changed;
- }
- internal bool SetActivityName(string name) {
- bool changed = false;
- var activity = GetActivityWithLaunchIntent() as XmlElement;
- if (activity.GetAttribute("name", AndroidXmlNamespace) != name) {
- activity.SetAttribute("name", AndroidXmlNamespace, name);
- changed = true;
+ // for api level 33
+ internal bool SetExported(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("exported", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("exported", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
}
- return changed;
- }
- internal bool AddCamera() {
- bool changed = false;
- if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nsMgr).Count == 0) {
- var elem = CreateElement("uses-permission");
- elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA"));
- ManifestElement.AppendChild(elem);
- changed = true;
- }
- if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nsMgr).Count == 0) {
- var elem = CreateElement("uses-feature");
- elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera"));
- ManifestElement.AppendChild(elem);
- changed = true;
- }
- return changed;
- }
+ internal bool SetWindowSoftInputMode(string mode) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("windowSoftInputMode", AndroidXmlNamespace) != mode) {
+ activity.SetAttribute("windowSoftInputMode", AndroidXmlNamespace, mode);
+ changed = true;
+ }
+ return changed;
+ }
- internal bool AddMicrophone() {
- bool changed = false;
- if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nsMgr).Count == 0) {
- var elem = CreateElement("uses-permission");
- elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE"));
- ManifestElement.AppendChild(elem);
- changed = true;
- }
- if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nsMgr).Count == 0) {
- var elem = CreateElement("uses-feature");
- elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone"));
- ManifestElement.AppendChild(elem);
- changed = true;
- }
- return changed;
+ internal bool SetHardwareAccelerated(bool enabled) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
+ activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, (enabled) ? "true" : "false");
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool SetActivityName(string name) {
+ bool changed = false;
+ var activity = GetActivityWithLaunchIntent() as XmlElement;
+ if (activity == null) {
+ return false;
+ }
+ if (activity.GetAttribute("name", AndroidXmlNamespace) != name) {
+ activity.SetAttribute("name", AndroidXmlNamespace, name);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddFileProvider(string basePath) {
+ bool changed = false;
+ var authorities = PlayerSettings.applicationIdentifier + ".unitywebview.fileprovider";
+ if (SelectNodes("/manifest/application/provider[@android:authorities='" + authorities + "']", nsMgr).Count == 0) {
+ var elem = CreateElement("provider");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "androidx.core.content.FileProvider"));
+ elem.Attributes.Append(CreateAndroidAttribute("authorities", authorities));
+ elem.Attributes.Append(CreateAndroidAttribute("exported", "false"));
+ elem.Attributes.Append(CreateAndroidAttribute("grantUriPermissions", "true"));
+ var meta = CreateElement("meta-data");
+ meta.Attributes.Append(CreateAndroidAttribute("name", "android.support.FILE_PROVIDER_PATHS"));
+ meta.Attributes.Append(CreateAndroidAttribute("resource", "@xml/unitywebview_file_provider_paths"));
+ elem.AppendChild(meta);
+ ApplicationElement.AppendChild(elem);
+ changed = true;
+ var xml = GetFileProviderSettingPath(basePath);
+ if (!File.Exists(xml)) {
+ Directory.CreateDirectory(Path.GetDirectoryName(xml));
+ File.WriteAllText(
+ xml,
+ "\n" +
+ " \n" +
+ "\n");
+ }
+ }
+ return changed;
+ }
+
+ private string GetFileProviderSettingPath(string basePath) {
+ var pathBuilder = new StringBuilder(basePath);
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("res");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("xml");
+ pathBuilder.Append(Path.DirectorySeparatorChar).Append("unitywebview_file_provider_paths.xml");
+ return pathBuilder.ToString();
+ }
+
+ internal bool AddCamera() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/data-storage/shared/media#media-location-permission
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.ACCESS_MEDIA_LOCATION']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.ACCESS_MEDIA_LOCATION"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.IMAGE_CAPTURE']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.IMAGE_CAPTURE"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddGallery() {
+ bool changed = false;
+ // for api level 33
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_IMAGES']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_IMAGES"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_VIDEO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_VIDEO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://developer.android.com/training/package-visibility/declaring
+ if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
+ var elem = CreateElement("queries");
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.GET_CONTENT']", nsMgr).Count == 0) {
+ var action = CreateElement("action");
+ action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.GET_CONTENT"));
+ var intent = CreateElement("intent");
+ intent.AppendChild(action);
+ var queries = SelectSingleNode("/manifest/queries") as XmlElement;
+ queries.AppendChild(intent);
+ changed = true;
+ }
+ return changed;
+ }
+
+ internal bool AddMicrophone() {
+ bool changed = false;
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-feature");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ // cf. https://github.com/gree/unity-webview/issues/679
+ // cf. https://github.com/fluttercommunity/flutter_webview_plugin/issues/138#issuecomment-559307558
+ // cf. https://stackoverflow.com/questions/38917751/webview-webrtc-not-working/68024032#68024032
+ // cf. https://stackoverflow.com/questions/40236925/allowing-microphone-accesspermission-in-webview-android-studio-java/47410311#47410311
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MODIFY_AUDIO_SETTINGS']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MODIFY_AUDIO_SETTINGS"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.RECORD_AUDIO']", nsMgr).Count == 0) {
+ var elem = CreateElement("uses-permission");
+ elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.RECORD_AUDIO"));
+ ManifestElement.AppendChild(elem);
+ changed = true;
+ }
+ return changed;
+ }
}
}
+#endif
diff --git a/plugins/Mac/Sources/WebView.m b/plugins/Mac/Sources/WebView.m
deleted file mode 100644
index ac9d99fe..00000000
--- a/plugins/Mac/Sources/WebView.m
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
- * Copyright (C) 2011 Keijiro Takahashi
- * Copyright (C) 2012 GREE, Inc.
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- */
-
-#import
-#import
-#import
-#import
-#import
-#import
-
-static BOOL inEditor;
-
-@interface CWebViewPlugin : NSObject
-{
- WebView *webView;
- NSString *gameObject;
- NSBitmapImageRep *bitmap;
- int textureId;
- BOOL needsDisplay;
- NSMutableDictionary *customRequestHeader;
- NSMutableArray *messages;
-}
-@end
-
-@implementation CWebViewPlugin
-
-- (id)initWithGameObject:(const char *)gameObject_ transparent:(BOOL)transparent width:(int)width height:(int)height ua:(const char *)ua
-{
- self = [super init];
- messages = [[NSMutableArray alloc] init];
- customRequestHeader = [[NSMutableDictionary alloc] init];
- webView = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
- webView.hidden = YES;
- if (transparent) {
- [webView setDrawsBackground:NO];
- }
- [webView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
- [webView setFrameLoadDelegate:(id)self];
- [webView setPolicyDelegate:(id)self];
- gameObject = [NSString stringWithUTF8String:gameObject_];
- if (ua != NULL && strcmp(ua, "") != 0) {
- [webView setCustomUserAgent:[NSString stringWithUTF8String:ua]];
- }
- return self;
-}
-
-- (void)dispose
-{
- @synchronized(self) {
- if (webView != nil) {
- [webView setFrameLoadDelegate:nil];
- [webView setPolicyDelegate:nil];
- [webView stopLoading:nil];
- webView = nil;
- }
- if (gameObject != nil) {
- gameObject = nil;
- }
- if (bitmap != nil) {
- bitmap = nil;
- }
- if (messages != nil) {
- messages = nil;
- }
- }
-}
-
-- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
-{
- [self addMessage:[NSString stringWithFormat:@"E%@",[error description]]];
-}
-
-- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
-{
- [self addMessage:[NSString stringWithFormat:@"L%@",[[[[frame dataSource] request] URL] absoluteString]]];
-}
-
-- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener
-{
- NSString *url = [[request URL] absoluteString];
- if ([url hasPrefix:@"unity:"]) {
- [self addMessage:[NSString stringWithFormat:@"J%@",[url substringFromIndex:6]]];
- [listener ignore];
- } else {
- if ([customRequestHeader count] > 0) {
- bool isCustomized = YES;
-
- // Check for additional custom header.
- for (NSString *key in [customRequestHeader allKeys])
- {
- if (![[[request allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
- isCustomized = NO;
- break;
- }
- }
-
- // If the custom header is not attached, give it and make a request again.
- if (!isCustomized) {
- [listener ignore];
- [frame loadRequest:[self constructionCustomHeader:request]];
- return;
- }
- }
- [self addMessage:[NSString stringWithFormat:@"S%@",url]];
- [listener use];
- }
-}
-
-- (void)addMessage:(NSString*)msg
-{
- @synchronized(messages)
- {
- [messages addObject:msg];
- }
-}
-
-- (NSString *)getMessage
-{
- NSString *ret = nil;
- @synchronized(messages)
- {
- if ([messages count] > 0) {
- ret = [messages[0] copy];
- [messages removeObjectAtIndex:0];
- }
- }
- return ret;
-}
-
-- (void)setRect:(int)width height:(int)height
-{
- if (webView == nil)
- return;
- NSRect frame;
- frame.size.width = width;
- frame.size.height = height;
- frame.origin.x = 0;
- frame.origin.y = 0;
- webView.frame = frame;
- if (bitmap != nil) {
- bitmap = nil;
- }
-}
-
-- (void)setVisibility:(BOOL)visibility
-{
- if (webView == nil)
- return;
- webView.hidden = visibility ? NO : YES;
-}
-
-- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
-{
- NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
- for (NSString *key in [customRequestHeader allKeys]) {
- [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
- }
- return convertedRequest;
-}
-
-- (void)loadURL:(const char *)url
-{
- if (webView == nil)
- return;
- NSString *urlStr = [NSString stringWithUTF8String:url];
- NSURL *nsurl = [NSURL URLWithString:urlStr];
- NSURLRequest *request = [self constructionCustomHeader:[NSMutableURLRequest requestWithURL:nsurl]];
- [[webView mainFrame] loadRequest:request];
-}
-
-- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
-{
- if (webView == nil)
- return;
- NSString *htmlStr = [NSString stringWithUTF8String:html];
- NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
- NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
- [[webView mainFrame] loadHTMLString:htmlStr baseURL:baseNSUrl];
-}
-
-- (void)evaluateJS:(const char *)js
-{
- if (webView == nil)
- return;
- NSString *jsStr = [NSString stringWithUTF8String:js];
- [webView stringByEvaluatingJavaScriptFromString:jsStr];
-}
-
-- (int)progress
-{
- if (webView == nil)
- return 0;
- return (int)([webView estimatedProgress] * 100);
-}
-
-- (BOOL)canGoBack
-{
- if (webView == nil)
- return false;
- return [webView canGoBack];
-}
-
-- (BOOL)canGoForward
-{
- if (webView == nil)
- return false;
- return [webView canGoForward];
-}
-
-- (void)goBack
-{
- if (webView == nil)
- return;
- [webView goBack];
-}
-
-- (void)goForward
-{
- if (webView == nil)
- return;
- [webView goForward];
-}
-
-- (void)update:(int)x y:(int)y deltaY:(float)deltaY buttonDown:(BOOL)buttonDown buttonPress:(BOOL)buttonPress buttonRelease:(BOOL)buttonRelease keyPress:(BOOL)keyPress keyCode:(unsigned short)keyCode keyChars:(const char*)keyChars refreshBitmap:(BOOL)refreshBitmap
-{
- if (webView == nil)
- return;
-
- NSView *view = [[[webView mainFrame] frameView] documentView];
- NSGraphicsContext *context = [NSGraphicsContext currentContext];
- NSEvent *event;
- NSString *characters;
-
- if (buttonDown) {
- if (buttonPress) {
- event = [NSEvent mouseEventWithType:NSLeftMouseDown
- location:NSMakePoint(x, y) modifierFlags:nil
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:nil clickCount:1 pressure:1];
- [view mouseDown:event];
- } else {
- event = [NSEvent mouseEventWithType:NSLeftMouseDragged
- location:NSMakePoint(x, y) modifierFlags:nil
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:nil clickCount:0 pressure:1];
- [view mouseDragged:event];
- }
- } else if (buttonRelease) {
- event = [NSEvent mouseEventWithType:NSLeftMouseUp
- location:NSMakePoint(x, y) modifierFlags:nil
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:nil clickCount:0 pressure:0];
- [view mouseUp:event];
- }
-
- if (keyPress) {
- characters = [NSString stringWithUTF8String:keyChars];
- event = [NSEvent keyEventWithType:NSKeyDown
- location:NSMakePoint(x, y) modifierFlags:nil
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context
- characters:characters
- charactersIgnoringModifiers:characters
- isARepeat:NO keyCode:(unsigned short)keyCode];
- [view keyDown:event];
- }
-
- if (deltaY != 0) {
- CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL,
- kCGScrollEventUnitLine, 1, deltaY * 3, 0);
- NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgEvent];
- CFRelease(cgEvent);
- [view scrollWheel:scrollEvent];
- }
-
- @synchronized(self) {
- if (refreshBitmap) {
- if (bitmap == nil) {
- bitmap = [webView bitmapImageRepForCachingDisplayInRect:webView.frame];
- }
- memset([bitmap bitmapData], 0, [bitmap bytesPerRow] * [bitmap pixelsHigh]);
- [webView cacheDisplayInRect:webView.frame toBitmapImageRep:bitmap];
- }
- needsDisplay = refreshBitmap;
- }
-}
-
-- (int)bitmapWide
-{
- @synchronized(self) {
- return (bitmap == nil) ? 0 : (int)[bitmap pixelsWide];
- }
-}
-
-- (int)bitmapHigh
-{
- @synchronized(self) {
- return (bitmap == nil) ? 0 : (int)[bitmap pixelsHigh];
- }
-}
-
-- (void)setTextureId:(int)tId
-{
- @synchronized(self) {
- textureId = tId;
- }
-}
-
-- (void)render
-{
- @synchronized(self) {
- if (webView == nil || !needsDisplay || bitmap == nil) {
- return;
- }
- int samplesPerPixel = (int)[bitmap samplesPerPixel];
- int rowLength = 0;
- int unpackAlign = 0;
- glGetIntegerv(GL_UNPACK_ROW_LENGTH, &rowLength);
- glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpackAlign);
- glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)[bitmap bytesPerRow] / samplesPerPixel);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glBindTexture(GL_TEXTURE_2D, textureId);
- if (![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)) {
- glTexSubImage2D(
- GL_TEXTURE_2D,
- 0,
- 0,
- 0,
- (GLsizei)[bitmap pixelsWide],
- (GLsizei)[bitmap pixelsHigh],
- samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
- GL_UNSIGNED_BYTE,
- [bitmap bitmapData]);
- }
- glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength);
- glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlign);
- }
-}
-
-- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
- NSString *valueString = [NSString stringWithUTF8String:headerValue];
-
- [customRequestHeader setObject:valueString forKey:keyString];
-}
-
-- (void)removeCustomRequestHeader:(const char *)headerKey
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
-
- if ([[customRequestHeader allKeys]containsObject:keyString]) {
- [customRequestHeader removeObjectForKey:keyString];
- }
-}
-
-- (void)clearCustomRequestHeader
-{
- [customRequestHeader removeAllObjects];
-}
-
-- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
- NSString *result = [customRequestHeader objectForKey:keyString];
- if (!result) {
- return NULL;
- }
-
- const char *s = [result UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
-
-@end
-
-typedef void (*UnityRenderEventFunc)(int eventId);
-#ifdef __cplusplus
-extern "C" {
-#endif
- const char *_CWebViewPlugin_GetAppPath(void);
- void *_CWebViewPlugin_Init(
- const char *gameObject, BOOL transparent, int width, int height, const char *ua, BOOL ineditor);
- void _CWebViewPlugin_Destroy(void *instance);
- void _CWebViewPlugin_SetRect(void *instance, int width, int height);
- void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
- void _CWebViewPlugin_LoadURL(void *instance, const char *url);
- void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
- void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
- int _CWebViewPlugin_Progress(void *instance);
- BOOL _CWebViewPlugin_CanGoBack(void *instance);
- BOOL _CWebViewPlugin_CanGoForward(void *instance);
- void _CWebViewPlugin_GoBack(void *instance);
- void _CWebViewPlugin_GoForward(void *instance);
- void _CWebViewPlugin_Update(void *instance, int x, int y, float deltaY,
- BOOL buttonDown, BOOL buttonPress, BOOL buttonRelease,
- BOOL keyPress, unsigned char keyCode, const char *keyChars, BOOL refreshBitmap);
- int _CWebViewPlugin_BitmapWidth(void *instance);
- int _CWebViewPlugin_BitmapHeight(void *instance);
- void _CWebViewPlugin_SetTextureId(void *instance, int textureId);
- void _CWebViewPlugin_SetCurrentInstance(void *instance);
- void UnityRenderEvent(int eventId);
- UnityRenderEventFunc GetRenderEventFunc(void);
- void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
- void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
- void _CWebViewPlugin_ClearCustomHeader(void *instance);
- const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
- const char *_CWebViewPlugin_GetMessage(void *instance);
-#ifdef __cplusplus
-}
-#endif
-
-const char *_CWebViewPlugin_GetAppPath(void)
-{
- const char *s = [[[[NSBundle mainBundle] bundleURL] absoluteString] UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
-
-static NSMutableSet *pool;
-
-void *_CWebViewPlugin_Init(
- const char *gameObject, BOOL transparent, int width, int height, const char *ua, BOOL ineditor)
-{
- if (pool == 0) {
- pool = [[NSMutableSet alloc] init];
- }
- inEditor = ineditor;
- CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObject:gameObject transparent:transparent width:width height:height ua:ua];
- [pool addObject:webViewPlugin];
- return (__bridge_retained void *)webViewPlugin;
-}
-
-void _CWebViewPlugin_Destroy(void *instance)
-{
- _CWebViewPlugin_SetCurrentInstance(NULL);
- CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
- [pool removeObject:webViewPlugin];
- [webViewPlugin dispose];
- webViewPlugin = nil;
-}
-
-void _CWebViewPlugin_SetRect(void *instance, int width, int height)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setRect:width height:height];
-}
-
-void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setVisibility:visibility];
-}
-
-void _CWebViewPlugin_LoadURL(void *instance, const char *url)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin loadURL:url];
-}
-
-void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin loadHTML:html baseURL:baseUrl];
-}
-
-void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin evaluateJS:js];
-}
-
-int _CWebViewPlugin_Progress(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin progress];
-}
-
-BOOL _CWebViewPlugin_CanGoBack(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin canGoBack];
-}
-
-BOOL _CWebViewPlugin_CanGoForward(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin canGoForward];
-}
-
-void _CWebViewPlugin_GoBack(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin goBack];
-}
-
-void _CWebViewPlugin_GoForward(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin goForward];
-}
-
-void _CWebViewPlugin_Update(void *instance, int x, int y, float deltaY,
- BOOL buttonDown, BOOL buttonPress, BOOL buttonRelease,
- BOOL keyPress, unsigned char keyCode, const char *keyChars, BOOL refreshBitmap)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin update:x y:y deltaY:deltaY buttonDown:buttonDown
- buttonPress:buttonPress buttonRelease:buttonRelease keyPress:keyPress
- keyCode:keyCode keyChars:keyChars refreshBitmap:refreshBitmap];
-}
-
-int _CWebViewPlugin_BitmapWidth(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin bitmapWide];
-}
-
-int _CWebViewPlugin_BitmapHeight(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin bitmapHigh];
-}
-
-void _CWebViewPlugin_SetTextureId(void *instance, int textureId)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setTextureId:textureId];
-}
-
-static void *_instance;
-
-void _CWebViewPlugin_SetCurrentInstance(void *instance)
-{
- _instance = instance;
-}
-
-void UnityRenderEvent(int eventId)
-{
- if (_instance == nil) {
- return;
- }
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)_instance;
- _instance = nil;
- if ([pool containsObject:webViewPlugin]) {
- [webViewPlugin render];
- }
-}
-
-UnityRenderEventFunc GetRenderEventFunc(void)
-{
- return UnityRenderEvent;
-}
-
-void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
-}
-
-void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin removeCustomRequestHeader:headerKey];
-}
-
-const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin getCustomRequestHeaderValue:headerKey];
-}
-
-void _CWebViewPlugin_ClearCustomHeader(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin clearCustomRequestHeader];
-}
-
-const char *_CWebViewPlugin_GetMessage(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- NSString *message = [webViewPlugin getMessage];
- if (message == nil) {
- return NULL;
- }
- const char *s = [message UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
diff --git a/plugins/Mac/Sources/WebView.mm b/plugins/Mac/Sources/WebView.mm
new file mode 100644
index 00000000..a42a8ec0
--- /dev/null
+++ b/plugins/Mac/Sources/WebView.mm
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (C) 2011 Keijiro Takahashi
+ * Copyright (C) 2012 GREE, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#import
+#import
+#import
+#import
+#import
+#import
+#include
+
+// cf. https://stackoverflow.com/questions/6303377/nswindow-set-frame-higher-than-screen/6303578#6303578
+@interface CNSWindow : NSWindow
+
+- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen;
+
+@end
+
+@implementation CNSWindow : NSWindow
+
+- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen;
+{
+ //return the unaltered frame, or do some other interesting things
+ return frameRect;
+}
+
+// Always report the window as visible so WKWebView keeps rendering even when the window is placed offscreen.
+// Otherwise the offscreen window is treated as occluded (not visible) and WebKit suppresses presentation
+// updates (layer commits). As a result, content after a page transition is never committed to the
+// WindowServer, and takeSnapshotWithConfiguration returns the old (pre-transition) content.
+// cf. isViewVisible() in WebKit's PageClientImplMac.mm reads viewWindow.occlusionState directly.
+- (NSWindowOcclusionState)occlusionState
+{
+ return NSWindowOcclusionStateVisible;
+}
+
+@end
+
+// cf. https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak/33365424#33365424
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
+
+static BOOL s_inEditor;
+static BOOL s_useMetal;
+
+@interface CWebViewPlugin : NSObject
+{
+ NSWindow *window;
+ NSWindowController *windowController;
+ WKWebView *webView;
+ NSString *gameObject;
+ NSBitmapImageRep *bitmap;
+ BOOL needsDisplay;
+ NSMutableDictionary *customRequestHeader;
+ NSMutableArray *messages;
+ NSRegularExpression *allowRegex;
+ NSRegularExpression *denyRegex;
+ NSRegularExpression *hookRegex;
+ BOOL inRendering;
+ int devicePixelRatio0;
+ NSInteger eventNumber0; // eventNumber for NSEvent (incremented each time to keep it unique)
+ NSInteger clickCount0; // click count (shared by mouse down/up; used for double-click detection)
+ NSTimeInterval lastClickTime0; // timestamp of the last mouse down (for double-click detection)
+ NSPoint lastClickPos0; // position of the last mouse down (for double-click detection)
+}
+@end
+
+@implementation CWebViewPlugin
+
+static WKProcessPool *_sharedProcessPool;
+static NSMutableArray *_instances = [[NSMutableArray alloc] init];
+static std::unordered_map _nskey2cgkey{
+ { NSUpArrowFunctionKey, 126 },
+ { NSDownArrowFunctionKey, 125 },
+ { NSLeftArrowFunctionKey, 123 },
+ { NSRightArrowFunctionKey, 124 },
+ { NSF1FunctionKey, 0 },
+ { NSF2FunctionKey, 0 },
+ { NSF3FunctionKey, 0 },
+ { NSF4FunctionKey, 0 },
+ { NSF5FunctionKey, 0 },
+ { NSF6FunctionKey, 0 },
+ { NSF7FunctionKey, 0 },
+ { NSF8FunctionKey, 0 },
+ { NSF9FunctionKey, 0 },
+ { NSF10FunctionKey, 0 },
+ { NSF11FunctionKey, 0 },
+ { NSF12FunctionKey, 0 },
+ { NSF13FunctionKey, 0 },
+ { NSF14FunctionKey, 0 },
+ { NSF15FunctionKey, 0 },
+ { NSF16FunctionKey, 0 },
+ { NSF17FunctionKey, 0 },
+ { NSF18FunctionKey, 0 },
+ { NSF19FunctionKey, 0 },
+ { NSF20FunctionKey, 0 },
+ { NSF21FunctionKey, 0 },
+ { NSF22FunctionKey, 0 },
+ { NSF23FunctionKey, 0 },
+ { NSF24FunctionKey, 0 },
+ { NSF25FunctionKey, 0 },
+ { NSF26FunctionKey, 0 },
+ { NSF27FunctionKey, 0 },
+ { NSF28FunctionKey, 0 },
+ { NSF29FunctionKey, 0 },
+ { NSF30FunctionKey, 0 },
+ { NSF31FunctionKey, 0 },
+ { NSF32FunctionKey, 0 },
+ { NSF33FunctionKey, 0 },
+ { NSF34FunctionKey, 0 },
+ { NSF35FunctionKey, 0 },
+ { NSInsertFunctionKey, 0 },
+ { NSDeleteFunctionKey, 0 },
+ { NSHomeFunctionKey, 0 },
+ { NSBeginFunctionKey, 0 },
+ { NSEndFunctionKey, 0 },
+ { NSPageUpFunctionKey, 0 },
+ { NSPageDownFunctionKey, 0 },
+ { NSPrintScreenFunctionKey, 0 },
+ { NSScrollLockFunctionKey, 0 },
+ { NSPauseFunctionKey, 0 },
+ { NSSysReqFunctionKey, 0 },
+ { NSBreakFunctionKey, 0 },
+ { NSResetFunctionKey, 0 },
+ { NSStopFunctionKey, 0 },
+ { NSMenuFunctionKey, 0 },
+ { NSUserFunctionKey, 0 },
+ { NSSystemFunctionKey, 0 },
+ { NSPrintFunctionKey, 0 },
+ { NSClearLineFunctionKey, 0 },
+ { NSClearDisplayFunctionKey, 0 },
+ { NSInsertLineFunctionKey, 0 },
+ { NSDeleteLineFunctionKey, 0 },
+ { NSInsertCharFunctionKey, 0 },
+ { NSDeleteCharFunctionKey, 0 },
+ { NSPrevFunctionKey, 0 },
+ { NSNextFunctionKey, 0 },
+ { NSSelectFunctionKey, 0 },
+ { NSExecuteFunctionKey, 0 },
+ { NSUndoFunctionKey, 0 },
+ { NSRedoFunctionKey, 0 },
+ { NSFindFunctionKey, 0 },
+ { NSHelpFunctionKey, 0 },
+ { NSModeSwitchFunctionKey, 0 },
+};
+
+- (BOOL)isInitialized
+{
+ return webView != nil;
+}
+
+- (id)initWithGameObject:(const char *)gameObject_ transparent:(BOOL)transparent zoom:(BOOL)zoom width:(int)width height:(int)height ua:(const char *)ua separated:(BOOL)separated
+{
+ self = [super init];
+ @synchronized(self) {
+ if (_sharedProcessPool == NULL) {
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ }
+ }
+ messages = [[NSMutableArray alloc] init];
+ customRequestHeader = [[NSMutableDictionary alloc] init];
+ allowRegex = nil;
+ denyRegex = nil;
+ hookRegex = nil;
+ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *controller = [[WKUserContentController alloc] init];
+ WKPreferences *preferences = [[WKPreferences alloc] init];
+ preferences.javaScriptEnabled = true;
+ preferences.plugInsEnabled = true;
+ [controller addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"unityControl"];
+ {
+ NSString *str = @"\
+window.Unity = { \
+ call: function(msg) { \
+ window.webkit.messageHandlers.unityControl.postMessage(msg); \
+ } \
+}; \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ if (!zoom) {
+ NSString *str = @"\
+(function() { \
+ var meta = document.querySelector('meta[name=viewport]'); \
+ if (meta == null) { \
+ meta = document.createElement('meta'); \
+ meta.name = 'viewport'; \
+ } \
+ meta.content += ((meta.content.length > 0) ? ',' : '') + 'user-scalable=no'; \
+ var head = document.getElementsByTagName('head')[0]; \
+ head.appendChild(meta); \
+})(); \
+";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:str injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [controller addUserScript:script];
+ }
+ configuration.userContentController = controller;
+ configuration.processPool = _sharedProcessPool;
+ // configuration.preferences = preferences;
+ NSRect frame = NSMakeRect(0, 0, width, height);
+ webView = [[WKWebView alloc] initWithFrame:frame
+ configuration:configuration];
+ [[[webView configuration] preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
+ webView.UIDelegate = self;
+ webView.navigationDelegate = self;
+ webView.hidden = YES;
+ if (transparent) {
+ [webView setValue:@NO forKey:@"drawsBackground"];
+ }
+ // webView.translatesAutoresizingMaskIntoConstraints = NO;
+ [webView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
+ // [webView setFrameLoadDelegate:(id)self];
+ // [webView setPolicyDelegate:(id)self];
+ [webView addObserver:self forKeyPath: @"loading" options: NSKeyValueObservingOptionNew context:nil];
+ gameObject = [NSString stringWithUTF8String:gameObject_];
+ if (ua != NULL && strcmp(ua, "") != 0) {
+ [webView setCustomUserAgent:[NSString stringWithUTF8String:ua]];
+ }
+ window = [[((!separated) ? CNSWindow.class : NSWindow.class) alloc]
+ initWithContentRect:frame
+ styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ [window setContentView:webView];
+ [window orderFront:NSApp];
+ [window setDelegate:self];
+ window.titleVisibility = NSWindowTitleHidden;
+ window.titlebarAppearsTransparent = YES;
+ window.styleMask |= NSWindowStyleMaskFullSizeContentView;
+ if (!separated) {
+ window.movable = NO;
+ [window setFrameOrigin:NSMakePoint(-10000, -10000)];
+ //[window setLevel:NSSubmenuWindowLevel];
+ }
+ windowController = [[NSWindowController alloc] initWithWindow:window];
+ return self;
+}
+
+- (void)dispose
+{
+ @synchronized(self) {
+ if (webView != nil) {
+ WKWebView *webView0 = webView;
+ webView = nil;
+ // [webView setFrameLoadDelegate:nil];
+ // [webView setPolicyDelegate:nil];
+ webView0.UIDelegate = nil;
+ webView0.navigationDelegate = nil;
+ [((WKWebView *)webView0).configuration.userContentController removeScriptMessageHandlerForName:@"unityControl"];
+ [webView0 stopLoading];
+ [webView0 removeObserver:self forKeyPath:@"loading"];
+ }
+ if (window != nil) {
+ [window close];
+ }
+ gameObject = nil;
+ bitmap = nil;
+ window = nil;
+ windowController = nil;
+ hookRegex = nil;
+ denyRegex = nil;
+ allowRegex = nil;
+ customRequestHeader = nil;
+ messages = nil;
+ }
+}
+
++ (void)resetSharedProcessPool
+{
+ // cf. https://stackoverflow.com/questions/33156567/getting-all-cookies-from-wkwebview/49744695#49744695
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ [_instances enumerateObjectsUsingBlock:^(CWebViewPlugin *obj, NSUInteger idx, BOOL *stop) {
+ if ([obj->webView isKindOfClass:[WKWebView class]]) {
+ WKWebView *webView = (WKWebView *)obj->webView;
+ webView.configuration.processPool = _sharedProcessPool;
+ }
+ }];
+}
+
++ (void)clearCookie:(const char *)name of:(const char *)url
+{
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ if (nsurl == nil) {
+ return;
+ }
+ NSString *nsname = [NSString stringWithUTF8String:name];
+ if (@available(macOS 10.11, *)) {
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ [array
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStore deleteCookie:cookie completionHandler:^{}];
+ }
+ }];
+ }];
+ } else {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.name isEqualToString:nsname]
+ && [cookie.domain isEqualToString:nsurl.host]
+ && [cookie.path isEqualToString:nsurl.path]) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }];
+ }
+}
+
++ (void)clearCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+
+ // cf. https://dev.classmethod.jp/smartphone/remove-webview-cookies/
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
+ NSString *cookiesPath = [libraryPath stringByAppendingPathComponent:@"Cookies"];
+ NSString *webKitPath = [libraryPath stringByAppendingPathComponent:@"WebKit"];
+ [[NSFileManager defaultManager] removeItemAtPath:cookiesPath error:nil];
+ [[NSFileManager defaultManager] removeItemAtPath:webKitPath error:nil];
+
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookies] enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [cookieStorage deleteCookie:cookie];
+ }];
+
+ NSOperatingSystemVersion version = { 10, 11, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ // cf. https://stackoverflow.com/questions/46465070/how-to-delete-cookies-from-wkhttpcookiestore/47928399#47928399
+ NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
+ modifiedSince:date
+ completionHandler:^{}];
+ }
+}
+
++ (void)saveCookies
+{
+ [CWebViewPlugin resetSharedProcessPool];
+}
+
+- (void)getCookies:(const char *)url
+{
+ NSOperatingSystemVersion version = { 10, 11, 0 };
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) {
+ NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+ WKHTTPCookieStore *cookieStore = WKWebsiteDataStore.defaultDataStore.httpCookieStore;
+ [cookieStore
+ getAllCookies:^(NSArray *array) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ [array enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ if ([cookie.domain isEqualToString:nsurl.host]) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }
+ }];
+ [self addMessage:[NSString stringWithFormat:@"CallOnCookies:%@",result]];
+ }];
+ } else {
+ [CWebViewPlugin resetSharedProcessPool];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
+ [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
+ NSMutableString *result = [NSMutableString string];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (cookieStorage == nil) {
+ // cf. https://stackoverflow.com/questions/33876295/nshttpcookiestorage-sharedhttpcookiestorage-comes-up-empty-in-10-11
+ cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"Cookies"];
+ }
+ [[cookieStorage cookiesForURL:[NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]]
+ enumerateObjectsUsingBlock:^(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
+ [result appendString:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]];
+ if ([cookie.domain length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Domain=%@", cookie.domain]];
+ }
+ if ([cookie.path length] > 0) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Path=%@", cookie.path]];
+ }
+ if (cookie.expiresDate != nil) {
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Expires=%@", [formatter stringFromDate:cookie.expiresDate]]];
+ }
+ [result appendString:[NSString stringWithFormat:@"; "]];
+ [result appendString:[NSString stringWithFormat:@"Version=%zd", cookie.version]];
+ [result appendString:[NSString stringWithFormat:@"\n"]];
+ }];
+ [self addMessage:[NSString stringWithFormat:@"CallOnCookies:%@",result]];
+ }
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ [self addMessage:[NSString stringWithFormat:@"CallOnError:%@",@"webViewWebContentProcessDidTerminate"]];
+}
+
+- (void)windowWillClose:(NSNotification *)notification
+{
+ [self addMessage:[NSString stringWithFormat:@"CallOnError:%@",@"windowWillClose"]];
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ [self addMessage:[NSString stringWithFormat:@"CallOnError:%@",[error description]]];
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+ [self addMessage:[NSString stringWithFormat:@"CallOnError:%@",[error description]]];
+}
+
+- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ if (webView == nil) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ NSString *url = [[navigationAction.request URL] absoluteString];
+ BOOL pass = YES;
+ if (allowRegex != nil && [allowRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = YES;
+ } else if (denyRegex != nil && [denyRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ pass = NO;
+ }
+ if (!pass) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ return;
+ }
+ if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ } else if ([url hasPrefix:@"unity:"]) {
+ [self addMessage:[NSString stringWithFormat:@"CallFromJS:%@",[url substringFromIndex:6]]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ } else if (hookRegex != nil && [hookRegex firstMatchInString:url options:0 range:NSMakeRange(0, url.length)]) {
+ [self addMessage:[NSString stringWithFormat:@"CallOnHooked:%@",url]];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
+ && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
+ // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
+ [webView loadRequest:navigationAction.request];
+ decisionHandler(WKNavigationActionPolicyCancel);
+ } else {
+ if (navigationAction.targetFrame != nil && navigationAction.targetFrame.isMainFrame) {
+ // If the custom header is not attached, give it and make a request again.
+ if (![self isSetupedCustomHeader:[navigationAction request]]) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ [webView loadRequest:[self constructionCustomHeader:navigationAction.request]];
+ return;
+ }
+ }
+ [self addMessage:[NSString stringWithFormat:@"CallOnStarted:%@",url]];
+ decisionHandler(WKNavigationActionPolicyAllow);
+ }
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode >= 400) {
+ [self addMessage:[NSString stringWithFormat:@"CallOnHttpError:%ld",(long)response.statusCode]];
+ }
+
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message {
+
+ // Log out the message received
+ //NSLog(@"Received event %@", message.body);
+ [self addMessage:[NSString stringWithFormat:@"CallFromJS:%@",message.body]];
+
+ /*
+ // Then pull something from the device using the message body
+ NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
+
+ // Execute some JavaScript using the result?
+ NSString *exec_template = @"set_headline(\"received: %@\");";
+ NSString *exec = [NSString stringWithFormat:exec_template, version];
+ [webView evaluateJavaScript:exec completionHandler:nil];
+ */
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (webView == nil)
+ return;
+
+ if ([keyPath isEqualToString:@"loading"] && [[change objectForKey:NSKeyValueChangeNewKey] intValue] == 0
+ && [webView URL] != nil) {
+ [self addMessage:[NSString stringWithFormat:@"CallOnLoaded:%@",[[webView URL] absoluteString]]];
+ }
+}
+
+- (void)addMessage:(NSString*)msg
+{
+ @synchronized(messages) {
+ [messages addObject:msg];
+ }
+}
+
+- (NSString *)getMessage
+{
+ NSString *ret = nil;
+ @synchronized(messages) {
+ if ([messages count] > 0) {
+ ret = [messages[0] copy];
+ [messages removeObjectAtIndex:0];
+ }
+ }
+ return ret;
+}
+
+- (void)setRect:(int)width height:(int)height
+{
+ if (webView == nil)
+ return;
+ NSRect frame;
+ frame.size.width = width;
+ frame.size.height = height;
+ frame.origin.x = 0;
+ frame.origin.y = 0;
+ webView.frame = frame;
+ bitmap = nil;
+ if (window != nil) {
+ frame.origin = window.frame.origin;
+ [window setFrame:frame display:YES];
+ }
+}
+
+- (void)setVisibility:(BOOL)visibility
+{
+ if (webView == nil)
+ return;
+ webView.hidden = visibility ? NO : YES;
+}
+
+- (BOOL)isSetupedCustomHeader:(NSURLRequest *)targetRequest
+{
+ // Check for additional custom header.
+ for (NSString *key in [customRequestHeader allKeys]) {
+ if (![[[targetRequest allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
+{
+ NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
+ for (NSString *key in [customRequestHeader allKeys]) {
+ [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
+ }
+ return convertedRequest;
+}
+
+- (BOOL)setURLPattern:(const char *)allowPattern and:(const char *)denyPattern and:(const char *)hookPattern
+{
+ NSError *err = nil;
+ NSRegularExpression *allow = nil;
+ NSRegularExpression *deny = nil;
+ NSRegularExpression *hook = nil;
+ if (allowPattern == nil || *allowPattern == '\0') {
+ allow = nil;
+ } else {
+ allow
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:allowPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (denyPattern == nil || *denyPattern == '\0') {
+ deny = nil;
+ } else {
+ deny
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:denyPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ if (hookPattern == nil || *hookPattern == '\0') {
+ hook = nil;
+ } else {
+ hook
+ = [NSRegularExpression
+ regularExpressionWithPattern:[NSString stringWithUTF8String:hookPattern]
+ options:0
+ error:&err];
+ if (err != nil) {
+ return NO;
+ }
+ }
+ allowRegex = allow;
+ denyRegex = deny;
+ hookRegex = hook;
+ return YES;
+}
+
+- (void)loadURL:(const char *)url
+{
+ if (webView == nil)
+ return;
+ NSString *urlStr = [NSString stringWithUTF8String:url];
+ NSURL *nsurl = [NSURL URLWithString:urlStr];
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
+
+ if ([nsurl.absoluteString hasPrefix:@"file:"]) {
+ NSURL *top = [NSURL URLWithString:[[nsurl absoluteString] stringByDeletingLastPathComponent]];
+ [webView loadFileURL:nsurl allowingReadAccessToURL:top];
+ } else {
+ [webView loadRequest:request];
+ }
+}
+
+- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
+{
+ if (webView == nil)
+ return;
+ NSString *htmlStr = [NSString stringWithUTF8String:html];
+ NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
+ NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
+ [webView loadHTMLString:htmlStr baseURL:baseNSUrl];
+}
+
+- (void)evaluateJS:(const char *)js
+{
+ if (webView == nil)
+ return;
+ NSString *jsStr = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:jsStr completionHandler:nil];
+}
+
+- (int)progress
+{
+ if (webView == nil)
+ return 0;
+ return (int)([webView estimatedProgress] * 100);
+}
+
+- (BOOL)canGoBack
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ if (webView == nil)
+ return false;
+ return [webView canGoForward];
+}
+
+- (void)goBack
+{
+ if (webView == nil)
+ return;
+ [webView goBack];
+}
+
+- (void)goForward
+{
+ if (webView == nil)
+ return;
+ [webView goForward];
+}
+
+- (void)reload
+{
+ if (webView == nil)
+ return;
+ [webView reload];
+}
+
+- (void)sendMouseEvent:(int)x y:(int)y deltaY:(float)deltaY mouseState:(int)mouseState
+{
+ if (webView == nil)
+ return;
+ NSView *view = webView;
+ NSGraphicsContext *context = [NSGraphicsContext currentContext];
+ // use the real window number (with 0, [event window] becomes nil and breaks AppKit's event routing)
+ NSInteger windowNumber = (window != nil) ? window.windowNumber : 0;
+ [self runBlock:^{
+ NSPoint location = NSMakePoint(x, y);
+ NSTimeInterval now = GetCurrentEventTime();
+ NSEvent *event;
+ switch (mouseState) {
+ case 1:
+ // double-click detection (cf. WebKit's updateClickCountForButton; increment if within 1s at the same position)
+ if (now - self->lastClickTime0 < 1.0 && NSEqualPoints(location, self->lastClickPos0)) {
+ self->clickCount0 += 1;
+ } else {
+ self->clickCount0 = 1;
+ }
+ self->lastClickTime0 = now;
+ self->lastClickPos0 = location;
+ event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
+ location:location modifierFlags:0
+ timestamp:now windowNumber:windowNumber
+ context:context eventNumber:++self->eventNumber0
+ clickCount:self->clickCount0 pressure:1];
+ // find the actual target subview via hitTest and dispatch to it (fall back to webView; cf. WebKit)
+ [([view hitTest:location] ?: view) mouseDown:event];
+ break;
+ case 2:
+ event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged
+ location:location modifierFlags:0
+ timestamp:now windowNumber:windowNumber
+ context:context eventNumber:++self->eventNumber0
+ clickCount:self->clickCount0 pressure:1];
+ // send drags directly to webView so drag-scrolling outside the view works (cf. WebKit)
+ [view mouseDragged:event];
+ break;
+ case 3:
+ // keep the mouse up clickCount the same as the mouse down (do not use 0)
+ event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
+ location:location modifierFlags:0
+ timestamp:now windowNumber:windowNumber
+ context:context eventNumber:++self->eventNumber0
+ clickCount:self->clickCount0 pressure:0];
+ [([view hitTest:location] ?: view) mouseUp:event];
+ break;
+ default:
+ // mouse move (hover); skip while scrolling (deltaY != 0)
+ if (deltaY == 0) {
+ event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
+ location:location modifierFlags:0
+ timestamp:now windowNumber:windowNumber
+ context:context eventNumber:++self->eventNumber0
+ clickCount:0 pressure:0];
+ // note: mouseMoved may be ignored for an offscreen WKWebView
+ [view mouseMoved:event];
+ }
+ break;
+ }
+ if (deltaY != 0) {
+ CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, deltaY * 3, 0);
+ NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgEvent];
+ CFRelease(cgEvent);
+ [view scrollWheel:scrollEvent];
+ }
+ }];
+}
+
+- (void)sendKeyEvent:(int)x y:(int)y keyChars:(char *)keyChars keyCode:(unsigned short)keyCode keyState:(int)keyState
+{
+ if (webView == nil)
+ return;
+ NSView *view = webView;
+ NSGraphicsContext *context = [NSGraphicsContext currentContext];
+ NSString *characters = [NSString stringWithUTF8String:keyChars];
+ CGKeyCode cgKeyCode = 0;
+ if (0xf700 <= keyCode && keyCode <= 0xf8ff
+ && _nskey2cgkey.find(keyCode) != _nskey2cgkey.end())
+ cgKeyCode = _nskey2cgkey.at(keyCode);
+ [self runBlock:^{
+ NSEvent *event;
+ switch (keyState) {
+ case 1:
+ event = [NSEvent keyEventWithType:NSEventTypeKeyDown
+ location:NSMakePoint(x, y) modifierFlags:0
+ timestamp:GetCurrentEventTime() windowNumber:0
+ context:context
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:NO keyCode:keyCode];
+ [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, cgKeyCode)) {
+ // [view keyDown:event];
+ // } else {
+ // [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // }
+ break;
+ case 2:
+ event = [NSEvent keyEventWithType:NSEventTypeKeyDown
+ location:NSMakePoint(x, y) modifierFlags:0
+ timestamp:GetCurrentEventTime() windowNumber:0
+ context:context
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:YES keyCode:keyCode];
+ [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, cgKeyCode)) {
+ // [view keyDown:event];
+ // } else {
+ // [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // }
+ break;
+ case 3:
+ event = [NSEvent keyEventWithType:NSEventTypeKeyUp
+ location:NSMakePoint(x, y) modifierFlags:0
+ timestamp:GetCurrentEventTime() windowNumber:0
+ context:context
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:NO keyCode:keyCode];
+ [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, cgKeyCode)) {
+ // [view keyDown:event];
+ // } else {
+ // [view interpretKeyEvents:[NSArray arrayWithObject:event]];
+ // }
+ break;
+ default:
+ break;
+ }
+ }];
+}
+
+- (void)update:(BOOL)refreshBitmap with:(int)devicePixelRatio
+{
+ if (webView == nil)
+ return;
+ if (refreshBitmap) {
+ @synchronized(self) {
+ if (inRendering)
+ return;
+ inRendering = YES;
+ }
+ if (devicePixelRatio < 1)
+ devicePixelRatio = 1;
+ else if (devicePixelRatio > 2)
+ devicePixelRatio = 2;
+ // [webView cacheDisplayInRect:webView.frame toBitmapImageRep:bitmap];
+ // bitmap = [webView bitmapImageRepForCachingDisplayInRect:webView.frame];
+ NSRect rect = webView.frame;
+ [self runBlock:^{
+ WKSnapshotConfiguration *config = [WKSnapshotConfiguration new];
+ config.rect = rect;
+ config.snapshotWidth = @(rect.size.width * devicePixelRatio);
+ [self->webView takeSnapshotWithConfiguration:config
+ completionHandler:^(NSImage *nsImg, NSError *err) {
+ NSBitmapImageRep *rep = nil;
+ if (err == nil) {
+ rep = (NSBitmapImageRep *)[nsImg bestRepresentationForRect:rect context:nil hints:nil];
+ if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
+ CGImageRef cgImg = [nsImg CGImageForProposedRect:NULL context:nil hints:nil];
+ rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImg];
+ }
+ }
+ @synchronized(self) {
+ if (rep) {
+ self->bitmap = rep;
+ }
+ self->needsDisplay = YES;
+ self->inRendering = NO;
+ }
+ }];
+ }];
+ }
+}
+
+- (void)runBlock:(void (^)())block
+{
+ block();
+ // if ([NSThread isMainThread]) {
+ // block();
+ // } else {
+ // dispatch_sync(dispatch_get_main_queue(), block);
+ // }
+}
+
+- (int)bitmapWide
+{
+ @synchronized(self) {
+ return (bitmap == nil) ? 0 : (int)[bitmap pixelsWide];
+ }
+}
+
+- (int)bitmapHigh
+{
+ @synchronized(self) {
+ return (bitmap == nil) ? 0 : (int)[bitmap pixelsHigh];
+ }
+}
+
+- (BOOL)bitmapARGB
+{
+ @synchronized(self) {
+ return (bitmap == nil) ? false : (([bitmap bitmapFormat] & NSBitmapFormatAlphaFirst) ? true : false);
+ }
+}
+
+- (void *)render:(void *)textureBuffer
+{
+ if (webView == nil)
+ return nil;
+ NSBitmapImageRep *bitmap0;
+ @synchronized(self) {
+ if (!needsDisplay)
+ return nil;
+ if (bitmap == nil)
+ return nil;
+ needsDisplay = NO;
+ bitmap0 = bitmap;
+ }
+ if (textureBuffer == nil) {
+ return (bitmap0 == nil) ? nil : (void *)[bitmap0 bitmapData];
+ } else {
+ unsigned char *data = [bitmap0 bitmapData];
+ long size = [bitmap0 pixelsHigh] * [bitmap0 bytesPerRow];
+ memcpy(textureBuffer, data, size);
+ return textureBuffer;
+ }
+}
+
+- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *valueString = [NSString stringWithUTF8String:headerValue];
+
+ [customRequestHeader setObject:valueString forKey:keyString];
+}
+
+- (void)removeCustomRequestHeader:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+
+ if ([[customRequestHeader allKeys]containsObject:keyString]) {
+ [customRequestHeader removeObjectForKey:keyString];
+ }
+}
+
+- (void)clearCustomRequestHeader
+{
+ [customRequestHeader removeAllObjects];
+}
+
+- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
+{
+ NSString *keyString = [NSString stringWithUTF8String:headerKey];
+ NSString *result = [customRequestHeader objectForKey:keyString];
+ if (!result) {
+ return NULL;
+ }
+
+ const char *s = [result UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+@end
+
+typedef void (*UnityRenderEventFunc)(int eventId);
+#ifdef __cplusplus
+extern "C" {
+#endif
+ const char *_CWebViewPlugin_GetAppPath(void);
+ void _CWebViewPlugin_InitStatic(BOOL inEditor, BOOL useMetal);
+ BOOL _CWebViewPlugin_IsInitialized(void *instance);
+ void *_CWebViewPlugin_Init(
+ const char *gameObject, BOOL transparent, BOOL zoom, int width, int height, const char *ua, BOOL separated);
+ void _CWebViewPlugin_Destroy(void *instance);
+ void _CWebViewPlugin_SetRect(void *instance, int width, int height);
+ void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
+ BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern);
+ void _CWebViewPlugin_LoadURL(void *instance, const char *url);
+ void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
+ void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
+ int _CWebViewPlugin_Progress(void *instance);
+ BOOL _CWebViewPlugin_CanGoBack(void *instance);
+ BOOL _CWebViewPlugin_CanGoForward(void *instance);
+ void _CWebViewPlugin_GoBack(void *instance);
+ void _CWebViewPlugin_GoForward(void *instance);
+ void _CWebViewPlugin_Reload(void *instance);
+ void _CWebViewPlugin_SendMouseEvent(void *instance, int x, int y, float deltaY, int mouseState);
+ void _CWebViewPlugin_SendKeyEvent(void *instance, int x, int y, char *keyChars, unsigned short keyCode, int keyState);
+ void _CWebViewPlugin_Update(void *instance, BOOL refreshBitmap, int devicePixelRatio);
+ int _CWebViewPlugin_BitmapWidth(void *instance);
+ int _CWebViewPlugin_BitmapHeight(void *instance);
+ BOOL _CWebViewPlugin_BitmapARGB(void *instance);
+ void *_CWebViewPlugin_Render(void *instance, void *textureBuffer);
+ void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
+ void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCustomHeader(void *instance);
+ const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
+ void _CWebViewPlugin_ClearCookie(const char *url, const char *name);
+ void _CWebViewPlugin_ClearCookies();
+ void _CWebViewPlugin_SaveCookies();
+ void _CWebViewPlugin_GetCookies(void *instance, const char *url);
+ const char *_CWebViewPlugin_GetMessage(void *instance);
+#ifdef __cplusplus
+}
+#endif
+
+const char *_CWebViewPlugin_GetAppPath(void)
+{
+ const char *s = [[[[NSBundle mainBundle] bundleURL] absoluteString] UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
+
+void _CWebViewPlugin_InitStatic(BOOL inEditor, BOOL useMetal)
+{
+ s_inEditor = inEditor;
+ s_useMetal = useMetal;
+}
+
+BOOL _CWebViewPlugin_IsInitialized(void *instance)
+{
+ if (instance == NULL)
+ return NO;
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin isInitialized];
+}
+
+void *_CWebViewPlugin_Init(
+ const char *gameObject, BOOL transparent, BOOL zoom, int width, int height, const char *ua, BOOL separated)
+{
+ CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObject:gameObject transparent:transparent zoom:zoom width:width height:height ua:ua separated:separated];
+ [_instances addObject:webViewPlugin];
+ return (__bridge_retained void *)webViewPlugin;
+}
+
+void _CWebViewPlugin_Destroy(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
+ [_instances removeObject:webViewPlugin];
+ [webViewPlugin dispose];
+ webViewPlugin = nil;
+}
+
+void _CWebViewPlugin_SetRect(void *instance, int width, int height)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setRect:width height:height];
+}
+
+void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin setVisibility:visibility];
+}
+
+BOOL _CWebViewPlugin_SetURLPattern(void *instance, const char *allowPattern, const char *denyPattern, const char *hookPattern)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin setURLPattern:allowPattern and:denyPattern and:hookPattern];
+}
+
+void _CWebViewPlugin_LoadURL(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadURL:url];
+}
+
+void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin loadHTML:html baseURL:baseUrl];
+}
+
+void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin evaluateJS:js];
+}
+
+int _CWebViewPlugin_Progress(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin progress];
+}
+
+BOOL _CWebViewPlugin_CanGoBack(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoBack];
+}
+
+BOOL _CWebViewPlugin_CanGoForward(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin canGoForward];
+}
+
+void _CWebViewPlugin_GoBack(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goBack];
+}
+
+void _CWebViewPlugin_GoForward(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin goForward];
+}
+
+void _CWebViewPlugin_Reload(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin reload];
+}
+
+void _CWebViewPlugin_SendMouseEvent(void *instance, int x, int y, float deltaY, int mouseState)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin sendMouseEvent:x y:y deltaY:deltaY mouseState:mouseState];
+}
+
+void _CWebViewPlugin_SendKeyEvent(void *instance, int x, int y, char *keyChars, unsigned short keyCode, int keyState)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin sendKeyEvent:x y:y keyChars:keyChars keyCode:keyCode keyState:keyState];
+}
+
+void _CWebViewPlugin_Update(void *instance, BOOL refreshBitmap, int devicePixelRatio)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin update:refreshBitmap with:devicePixelRatio];
+}
+
+int _CWebViewPlugin_BitmapWidth(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin bitmapWide];
+}
+
+int _CWebViewPlugin_BitmapHeight(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin bitmapHigh];
+}
+
+BOOL _CWebViewPlugin_BitmapARGB(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin bitmapARGB];
+}
+
+void *_CWebViewPlugin_Render(void *instance, void *textureBuffer)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin render:textureBuffer];
+}
+
+void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
+}
+
+void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin removeCustomRequestHeader:headerKey];
+}
+
+const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ return [webViewPlugin getCustomRequestHeaderValue:headerKey];
+}
+
+void _CWebViewPlugin_ClearCustomHeader(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin clearCustomRequestHeader];
+}
+
+void _CWebViewPlugin_ClearCookie(const char *url, const char *name)
+{
+ [CWebViewPlugin clearCookie:name of:url];
+}
+
+void _CWebViewPlugin_ClearCookies()
+{
+ [CWebViewPlugin clearCookies];
+}
+
+void _CWebViewPlugin_SaveCookies()
+{
+ [CWebViewPlugin saveCookies];
+}
+
+void _CWebViewPlugin_GetCookies(void *instance, const char *url)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ [webViewPlugin getCookies:url];
+}
+
+const char *_CWebViewPlugin_GetMessage(void *instance)
+{
+ CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
+ NSString *message = [webViewPlugin getMessage];
+ if (message == nil)
+ return NULL;
+ const char *s = [message UTF8String];
+ char *r = (char *)malloc(strlen(s) + 1);
+ strcpy(r, s);
+ return r;
+}
diff --git a/plugins/Mac/Sources/WebViewSeparated.m b/plugins/Mac/Sources/WebViewSeparated.m
deleted file mode 100644
index 31975036..00000000
--- a/plugins/Mac/Sources/WebViewSeparated.m
+++ /dev/null
@@ -1,715 +0,0 @@
-/*
- * Copyright (C) 2011 Keijiro Takahashi
- * Copyright (C) 2012 GREE, Inc.
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- */
-
-#import
-#import
-#import
-#import
-#import
-#import
-
-static BOOL inEditor;
-
-@interface CWebViewPlugin : NSObject
-{
- NSWindow *window;
- NSWindowController *windowController;
- WKWebView *webView;
- NSString *gameObject;
- NSBitmapImageRep *bitmap;
- int textureId;
- BOOL needsDisplay;
- NSMutableDictionary *customRequestHeader;
- NSMutableArray *messages;
-}
-@end
-
-@implementation CWebViewPlugin
-
-static WKProcessPool *_sharedProcessPool;
-
-- (id)initWithGameObject:(const char *)gameObject_ transparent:(BOOL)transparent width:(int)width height:(int)height ua:(const char *)ua
-{
- self = [super init];
- @synchronized(self) {
- if (_sharedProcessPool == NULL) {
- _sharedProcessPool = [[WKProcessPool alloc] init];
- }
- }
- messages = [[NSMutableArray alloc] init];
- customRequestHeader = [[NSMutableDictionary alloc] init];
- WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
- WKUserContentController *controller = [[WKUserContentController alloc] init];
- WKPreferences *preferences = [[WKPreferences alloc] init];
- preferences.javaScriptEnabled = true;
- preferences.plugInsEnabled = true;
- [controller addScriptMessageHandler:self name:@"unityControl"];
- configuration.userContentController = controller;
- configuration.processPool = _sharedProcessPool;
- // configuration.preferences = preferences;
- NSRect frame = NSMakeRect(0, 0, width, height);
- webView = [[WKWebView alloc] initWithFrame:frame
- configuration:configuration];
- [[[webView configuration] preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
- webView.UIDelegate = self;
- webView.navigationDelegate = self;
- webView.hidden = NO;
- if (transparent) {
- // [webView setDrawsBackground:NO];
- }
- [webView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
- // [webView setFrameLoadDelegate:(id)self];
- // [webView setPolicyDelegate:(id)self];
- webView.UIDelegate = self;
- webView.navigationDelegate = self;
- gameObject = [NSString stringWithUTF8String:gameObject_];
- if (ua != NULL && strcmp(ua, "") != 0) {
- [webView setCustomUserAgent:[NSString stringWithUTF8String:ua]];
- }
-
- window = [[NSWindow alloc] initWithContentRect:frame
- styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable
- backing:NSBackingStoreBuffered
- defer:NO];
- [window setContentView:webView];
- [window orderFront:NSApp];
- windowController = [[NSWindowController alloc] initWithWindow:window];
-
- return self;
-}
-
-- (void)dispose
-{
- @synchronized(self) {
- if (webView != nil) {
- // [webView setFrameLoadDelegate:nil];
- // [webView setPolicyDelegate:nil];
- webView.UIDelegate = nil;
- webView.navigationDelegate = nil;
- [webView stopLoading:nil];
- webView = nil;
- }
- if (gameObject != nil) {
- gameObject = nil;
- }
- if (bitmap != nil) {
- bitmap = nil;
- }
- if (window != nil) {
- window = nil;
- }
- if (windowController != nil){
- windowController = nil;
- }
- if (messages != nil) {
- messages = nil;
- }
- }
-}
-
-/*
-- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
-{
- [self addMessage:[NSString stringWithFormat:@"E%@",[error description]]];
-}
-
-- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
-{
- [self addMessage:[NSString stringWithFormat:@"L%@",[[[[frame dataSource] request] URL] absoluteString]]];
-}
-
-- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener
-{
- NSString *url = [[request URL] absoluteString];
- if ([url hasPrefix:@"unity:"]) {
- [self addMessage:[NSString stringWithFormat:@"J%@",[url substringFromIndex:6]]];
- [listener ignore];
- } else {
- if ([customRequestHeader count] > 0) {
- bool isCustomized = YES;
-
- // Check for additional custom header.
- for (NSString *key in [customRequestHeader allKeys])
- {
- if (![[[request allHTTPHeaderFields] objectForKey:key] isEqualToString:[customRequestHeader objectForKey:key]]) {
- isCustomized = NO;
- break;
- }
- }
-
- // If the custom header is not attached, give it and make a request again.
- if (!isCustomized) {
- [listener ignore];
- [frame loadRequest:[self constructionCustomHeader:request]];
- return;
- }
- }
-
- [listener use];
- }
-}
-*/
-
-- (void)webView:(WKWebView*)wkWebView didCommitNavigation:(null_unspecified WKNavigation *)navigation
-{
- [self addMessage:[NSString stringWithFormat:@"L%s","Unknown URL"]];
-}
-
-- (void)webView:(WKWebView *)wkWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
-{
- if (webView == nil) {
- decisionHandler(WKNavigationActionPolicyCancel);
- return;
- }
- NSString *url = [[navigationAction.request URL] absoluteString];
- //if ([url rangeOfString:@"//itunes.apple.com/"].location != NSNotFound) {
- // [[UIApplication sharedApplication] openURL:url];
- // decisionHandler(WKNavigationActionPolicyCancel);
- //} else
- if ([url hasPrefix:@"unity:"]) {
- [self addMessage:[NSString stringWithFormat:@"J%@",[url substringFromIndex:6]]];
- decisionHandler(WKNavigationActionPolicyCancel);
- } else if (navigationAction.navigationType == WKNavigationTypeLinkActivated
- && (!navigationAction.targetFrame || !navigationAction.targetFrame.isMainFrame)) {
- // cf. for target="_blank", cf. http://qiita.com/ShingoFukuyama/items/b3a1441025a36ab7659c
- [webView loadRequest:navigationAction.request];
- decisionHandler(WKNavigationActionPolicyCancel);
- } else {
- [self addMessage:[NSString stringWithFormat:@"S%@",url]];
- decisionHandler(WKNavigationActionPolicyAllow);
- }
-}
-
-
-- (void)userContentController:(WKUserContentController *)userContentController
- didReceiveScriptMessage:(WKScriptMessage *)message {
-
- // Log out the message received
- NSLog(@"Received event %@", message.body);
- [self addMessage:[NSString stringWithFormat:@"J%@",message.body]];
-
- /*
- // Then pull something from the device using the message body
- NSString *version = [[UIDevice currentDevice] valueForKey:message.body];
-
- // Execute some JavaScript using the result?
- NSString *exec_template = @"set_headline(\"received: %@\");";
- NSString *exec = [NSString stringWithFormat:exec_template, version];
- [webView evaluateJavaScript:exec completionHandler:nil];
- */
-}
-
-- (void)addMessage:(NSString*)msg
-{
- @synchronized(messages)
- {
- [messages addObject:msg];
- }
-}
-
-- (NSString *)getMessage
-{
- NSString *ret = nil;
- @synchronized(messages)
- {
- if ([messages count] > 0) {
- ret = [messages[0] copy];
- [messages removeObjectAtIndex:0];
- }
- }
- return ret;
-}
-
-- (void)setRect:(int)width height:(int)height
-{
- if (webView == nil)
- return;
- NSRect frame;
- frame.size.width = width;
- frame.size.height = height;
- // frame.origin.x = 0;
- // frame.origin.y = 0;
- // webView.frame = frame;
- if (bitmap != nil) {
- bitmap = nil;
- }
- frame.origin = window.frame.origin;
- [window setFrame:frame display:YES];
-}
-
-- (void)setVisibility:(BOOL)visibility
-{
- if (webView == nil)
- return;
- // webView.hidden = visibility ? NO : YES;
-}
-
-- (NSURLRequest *)constructionCustomHeader:(NSURLRequest *)originalRequest
-{
- NSMutableURLRequest *convertedRequest = originalRequest.mutableCopy;
- for (NSString *key in [customRequestHeader allKeys]) {
- [convertedRequest setValue:customRequestHeader[key] forHTTPHeaderField:key];
- }
- return convertedRequest;
-}
-
-- (void)loadURL:(const char *)url
-{
- if (webView == nil)
- return;
- NSString *urlStr = [NSString stringWithUTF8String:url];
- NSURL *nsurl = [NSURL URLWithString:urlStr];
- NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
-
- if ([nsurl.absoluteString hasPrefix:@"file:"]) {
- NSURL *top = [NSURL URLWithString:[[nsurl absoluteString] stringByDeletingLastPathComponent]];
- [webView loadFileURL:nsurl allowingReadAccessToURL:top];
- } else {
- [webView loadRequest:request];
- }
-}
-
-- (void)loadHTML:(const char *)html baseURL:(const char *)baseUrl
-{
- if (webView == nil)
- return;
- NSString *htmlStr = [NSString stringWithUTF8String:html];
- NSString *baseStr = [NSString stringWithUTF8String:baseUrl];
- NSURL *baseNSUrl = [NSURL URLWithString:baseStr];
- [webView loadHTMLString:htmlStr baseURL:baseNSUrl];
-}
-
-- (void)evaluateJS:(const char *)js
-{
- if (webView == nil)
- return;
- NSString *jsStr = [NSString stringWithUTF8String:js];
- [webView evaluateJavaScript:jsStr completionHandler:nil];
-}
-
-- (int)progress
-{
- if (webView == nil)
- return 0;
- return (int)([webView estimatedProgress] * 100);
-}
-
-- (BOOL)canGoBack
-{
- if (webView == nil)
- return false;
- return [webView canGoBack];
-}
-
-- (BOOL)canGoForward
-{
- if (webView == nil)
- return false;
- return [webView canGoForward];
-}
-
-- (void)goBack
-{
- if (webView == nil)
- return;
- [webView goBack];
-}
-
-- (void)goForward
-{
- if (webView == nil)
- return;
- [webView goForward];
-}
-
-- (void)update:(int)x y:(int)y deltaY:(float)deltaY buttonDown:(BOOL)buttonDown buttonPress:(BOOL)buttonPress buttonRelease:(BOOL)buttonRelease keyPress:(BOOL)keyPress keyCode:(unsigned short)keyCode keyChars:(const char*)keyChars refreshBitmap:(BOOL)refreshBitmap
-{
- if (webView == nil)
- return;
-
- NSView *view = webView;
- NSGraphicsContext *context = [NSGraphicsContext currentContext];
- NSEvent *event;
- NSString *characters;
-
- if (buttonDown) {
- if (buttonPress) {
- event = [NSEvent mouseEventWithType:NSLeftMouseDown
- location:NSMakePoint(x, y) modifierFlags:0
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:0 clickCount:1 pressure:1];
- [view mouseDown:event];
- } else {
- event = [NSEvent mouseEventWithType:NSLeftMouseDragged
- location:NSMakePoint(x, y) modifierFlags:0
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:0 clickCount:0 pressure:1];
- [view mouseDragged:event];
- }
- } else if (buttonRelease) {
- event = [NSEvent mouseEventWithType:NSLeftMouseUp
- location:NSMakePoint(x, y) modifierFlags:0
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context eventNumber:0 clickCount:0 pressure:0];
- [view mouseUp:event];
- }
-
- if (keyPress) {
- characters = [NSString stringWithUTF8String:keyChars];
- event = [NSEvent keyEventWithType:NSKeyDown
- location:NSMakePoint(x, y) modifierFlags:0
- timestamp:GetCurrentEventTime() windowNumber:0
- context:context
- characters:characters
- charactersIgnoringModifiers:characters
- isARepeat:NO keyCode:(unsigned short)keyCode];
- [view keyDown:event];
- }
-
- if (deltaY != 0) {
- CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL,
- kCGScrollEventUnitLine, 1, deltaY * 3, 0);
- NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgEvent];
- CFRelease(cgEvent);
- [view scrollWheel:scrollEvent];
- }
-
- @synchronized(self) {
- if (refreshBitmap) {
- if (bitmap == nil) {
- bitmap = [webView bitmapImageRepForCachingDisplayInRect:webView.frame];
- }
- memset([bitmap bitmapData], 128, [bitmap bytesPerRow] * [bitmap pixelsHigh]);
- // [webView cacheDisplayInRect:webView.frame toBitmapImageRep:bitmap];
- }
- needsDisplay = refreshBitmap;
- }
-}
-
-- (int)bitmapWide
-{
- @synchronized(self) {
- return (bitmap == nil) ? 0 : (int)[bitmap pixelsWide];
- }
-}
-
-- (int)bitmapHigh
-{
- @synchronized(self) {
- return (bitmap == nil) ? 0 : (int)[bitmap pixelsHigh];
- }
-}
-
-- (void)setTextureId:(int)tId
-{
- @synchronized(self) {
- textureId = tId;
- }
-}
-
-- (void)render
-{
- @synchronized(self) {
- if (webView == nil)
- return;
- if (!needsDisplay)
- return;
- if (bitmap == nil)
- return;
-
- int samplesPerPixel = (int)[bitmap samplesPerPixel];
- int rowLength = 0;
- int unpackAlign = 0;
- glGetIntegerv(GL_UNPACK_ROW_LENGTH, &rowLength);
- glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpackAlign);
- glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)[bitmap bytesPerRow] / samplesPerPixel);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glBindTexture(GL_TEXTURE_2D, textureId);
- if (![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)) {
- glTexSubImage2D(
- GL_TEXTURE_2D,
- 0,
- 0,
- 0,
- (GLsizei)[bitmap pixelsWide],
- (GLsizei)[bitmap pixelsHigh],
- samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
- GL_UNSIGNED_BYTE,
- [bitmap bitmapData]);
- }
- glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength);
- glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlign);
- }
-}
-
-- (void)addCustomRequestHeader:(const char *)headerKey value:(const char *)headerValue
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
- NSString *valueString = [NSString stringWithUTF8String:headerValue];
-
- [customRequestHeader setObject:valueString forKey:keyString];
-}
-
-- (void)removeCustomRequestHeader:(const char *)headerKey
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
-
- if ([[customRequestHeader allKeys]containsObject:keyString]) {
- [customRequestHeader removeObjectForKey:keyString];
- }
-}
-
-- (void)clearCustomRequestHeader
-{
- [customRequestHeader removeAllObjects];
-}
-
-- (const char *)getCustomRequestHeaderValue:(const char *)headerKey
-{
- NSString *keyString = [NSString stringWithUTF8String:headerKey];
- NSString *result = [customRequestHeader objectForKey:keyString];
- if (!result) {
- return NULL;
- }
-
- const char *s = [result UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
-
-@end
-
-typedef void (*UnityRenderEventFunc)(int eventId);
-#ifdef __cplusplus
-extern "C" {
-#endif
- const char *_CWebViewPlugin_GetAppPath(void);
- void *_CWebViewPlugin_Init(
- const char *gameObject, BOOL transparent, int width, int height, const char *ua, BOOL ineditor);
- void _CWebViewPlugin_Destroy(void *instance);
- void _CWebViewPlugin_SetRect(void *instance, int width, int height);
- void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility);
- void _CWebViewPlugin_LoadURL(void *instance, const char *url);
- void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl);
- void _CWebViewPlugin_EvaluateJS(void *instance, const char *url);
- int _CWebViewPlugin_Progress(void *instance);
- BOOL _CWebViewPlugin_CanGoBack(void *instance);
- BOOL _CWebViewPlugin_CanGoForward(void *instance);
- void _CWebViewPlugin_GoBack(void *instance);
- void _CWebViewPlugin_GoForward(void *instance);
- void _CWebViewPlugin_Update(void *instance, int x, int y, float deltaY,
- BOOL buttonDown, BOOL buttonPress, BOOL buttonRelease,
- BOOL keyPress, unsigned char keyCode, const char *keyChars, BOOL refreshBitmap);
- int _CWebViewPlugin_BitmapWidth(void *instance);
- int _CWebViewPlugin_BitmapHeight(void *instance);
- void _CWebViewPlugin_SetTextureId(void *instance, int textureId);
- void _CWebViewPlugin_SetCurrentInstance(void *instance);
- void UnityRenderEvent(int eventId);
- UnityRenderEventFunc GetRenderEventFunc(void);
- void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue);
- void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey);
- void _CWebViewPlugin_ClearCustomHeader(void *instance);
- const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey);
- const char *_CWebViewPlugin_GetMessage(void *instance);
-#ifdef __cplusplus
-}
-#endif
-
-const char *_CWebViewPlugin_GetAppPath(void)
-{
- const char *s = [[[[NSBundle mainBundle] bundleURL] absoluteString] UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
-
-static NSMutableSet *pool;
-
-void *_CWebViewPlugin_Init(
- const char *gameObject, BOOL transparent, int width, int height, const char *ua, BOOL ineditor)
-{
- if (pool == 0)
- pool = [[NSMutableSet alloc] init];
-
- inEditor = ineditor;
- CWebViewPlugin *webViewPlugin = [[CWebViewPlugin alloc] initWithGameObject:gameObject transparent:transparent width:width height:height ua:ua];
- [pool addObject:webViewPlugin];
- return (__bridge_retained void *)webViewPlugin;
-}
-
-void _CWebViewPlugin_Destroy(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge_transfer CWebViewPlugin *)instance;
- [pool removeObject:webViewPlugin];
- [webViewPlugin dispose];
- webViewPlugin = nil;
-}
-
-void _CWebViewPlugin_SetRect(void *instance, int width, int height)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setRect:width height:height];
-}
-
-void _CWebViewPlugin_SetVisibility(void *instance, BOOL visibility)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setVisibility:visibility];
-}
-
-void _CWebViewPlugin_LoadURL(void *instance, const char *url)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin loadURL:url];
-}
-
-void _CWebViewPlugin_LoadHTML(void *instance, const char *html, const char *baseUrl)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin loadHTML:html baseURL:baseUrl];
-}
-
-void _CWebViewPlugin_EvaluateJS(void *instance, const char *js)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin evaluateJS:js];
-}
-
-int _CWebViewPlugin_Progress(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin progress];
-}
-
-BOOL _CWebViewPlugin_CanGoBack(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin canGoBack];
-}
-
-BOOL _CWebViewPlugin_CanGoForward(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin canGoForward];
-}
-
-void _CWebViewPlugin_GoBack(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin goBack];
-}
-
-void _CWebViewPlugin_GoForward(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin goForward];
-}
-
-void _CWebViewPlugin_Update(void *instance, int x, int y, float deltaY,
- BOOL buttonDown, BOOL buttonPress, BOOL buttonRelease,
- BOOL keyPress, unsigned char keyCode, const char *keyChars, BOOL refreshBitmap)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin update:x y:y deltaY:deltaY buttonDown:buttonDown
- buttonPress:buttonPress buttonRelease:buttonRelease keyPress:keyPress
- keyCode:keyCode keyChars:keyChars refreshBitmap:refreshBitmap];
-}
-
-int _CWebViewPlugin_BitmapWidth(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin bitmapWide];
-}
-
-int _CWebViewPlugin_BitmapHeight(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin bitmapHigh];
-}
-
-void _CWebViewPlugin_SetTextureId(void *instance, int textureId)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin setTextureId:textureId];
-}
-
-static void *_instance;
-
-void _CWebViewPlugin_SetCurrentInstance(void *instance)
-{
- _instance = instance;
-}
-
-void UnityRenderEvent(int eventId)
-{
- @autoreleasepool {
- if (_instance == nil) {
- return;
- }
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)_instance;
- _instance = nil;
- if ([pool containsObject:webViewPlugin]) {
- [webViewPlugin render];
- }
- }
-}
-
-UnityRenderEventFunc GetRenderEventFunc(void)
-{
- return UnityRenderEvent;
-}
-
-void _CWebViewPlugin_AddCustomHeader(void *instance, const char *headerKey, const char *headerValue)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin addCustomRequestHeader:headerKey value:headerValue];
-}
-
-void _CWebViewPlugin_RemoveCustomHeader(void *instance, const char *headerKey)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin removeCustomRequestHeader:headerKey];
-}
-
-const char *_CWebViewPlugin_GetCustomHeaderValue(void *instance, const char *headerKey)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- return [webViewPlugin getCustomRequestHeaderValue:headerKey];
-}
-
-void _CWebViewPlugin_ClearCustomHeader(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- [webViewPlugin clearCustomRequestHeader];
-}
-
-const char *_CWebViewPlugin_GetMessage(void *instance)
-{
- CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance;
- NSString *message = [webViewPlugin getMessage];
- if (message == nil)
- return NULL;
- const char *s = [message UTF8String];
- char *r = (char *)malloc(strlen(s) + 1);
- strcpy(r, s);
- return r;
-}
diff --git a/plugins/Mac/WebView.bundle.meta b/plugins/Mac/WebView.bundle.meta
index 516d3cd6..42a688b6 100644
--- a/plugins/Mac/WebView.bundle.meta
+++ b/plugins/Mac/WebView.bundle.meta
@@ -38,7 +38,8 @@ PluginImporter:
CPU: AnyCPU
OSXUniversal:
enabled: 1
- settings: {}
+ settings:
+ CPU: AnyCPU
Win:
enabled: 0
settings:
diff --git a/plugins/Mac/WebView.xcodeproj/project.pbxproj b/plugins/Mac/WebView.xcodeproj/project.pbxproj
index d26fc14c..6a1393d3 100644
--- a/plugins/Mac/WebView.xcodeproj/project.pbxproj
+++ b/plugins/Mac/WebView.xcodeproj/project.pbxproj
@@ -7,23 +7,14 @@
objects = {
/* Begin PBXBuildFile section */
- 183C36501EA488E50071D97B /* WebViewSeparated.m in Sources */ = {isa = PBXBuildFile; fileRef = 183C364F1EA488E50071D97B /* WebViewSeparated.m */; };
- 18E975FA1EA4873F00083D49 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B8C53715108B89000C56DC /* Carbon.framework */; };
- 18E975FB1EA4873F00083D49 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B8C534151078DB000C56DC /* WebKit.framework */; };
- 18E975FC1EA4873F00083D49 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81F81AEC14D76D2400845D4C /* OpenGL.framework */; };
- 18E975FD1EA4873F00083D49 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81E2C20B14C5684A004CE5C2 /* Cocoa.framework */; };
- 18E975FF1EA4873F00083D49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8102525514C569D80022296D /* InfoPlist.strings */; };
8102525814C569D80022296D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8102525514C569D80022296D /* InfoPlist.strings */; };
81B8C535151078DB000C56DC /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B8C534151078DB000C56DC /* WebKit.framework */; };
81B8C53815108B89000C56DC /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B8C53715108B89000C56DC /* Carbon.framework */; };
81E2C20C14C5684A004CE5C2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81E2C20B14C5684A004CE5C2 /* Cocoa.framework */; };
- 81F4B4D914C6888B001B4465 /* WebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 81F4B4D814C6888B001B4465 /* WebView.m */; };
- 81F81AED14D76D2400845D4C /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81F81AEC14D76D2400845D4C /* OpenGL.framework */; };
+ 81F4B4D914C6888B001B4465 /* WebView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81F4B4D814C6888B001B4465 /* WebView.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
- 183C364F1EA488E50071D97B /* WebViewSeparated.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.objc; fileEncoding = 4; name = WebViewSeparated.m; path = Sources/WebViewSeparated.m; sourceTree = ""; };
- 18E976041EA4873F00083D49 /* WebViewSeparated.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebViewSeparated.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
18E976051EA4873F00083D49 /* Info-WebViewSeparated.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = "Info-WebViewSeparated.plist"; path = "Resources/Info-WebViewSeparated.plist"; sourceTree = ""; };
8102525414C569D80022296D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Resources/Info.plist; sourceTree = SOURCE_ROOT; };
8102525514C569D80022296D /* InfoPlist.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = Resources/InfoPlist.strings; sourceTree = SOURCE_ROOT; };
@@ -34,29 +25,17 @@
81E2C20B14C5684A004CE5C2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
81E2C20E14C5684A004CE5C2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
81E2C21014C5684A004CE5C2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
- 81F4B4D814C6888B001B4465 /* WebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WebView.m; path = Sources/WebView.m; sourceTree = ""; };
+ 81F4B4D814C6888B001B4465 /* WebView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WebView.mm; path = Sources/WebView.mm; sourceTree = ""; };
81F81AEC14D76D2400845D4C /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
- 18E975F91EA4873F00083D49 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 18E975FA1EA4873F00083D49 /* Carbon.framework in Frameworks */,
- 18E975FB1EA4873F00083D49 /* WebKit.framework in Frameworks */,
- 18E975FC1EA4873F00083D49 /* OpenGL.framework in Frameworks */,
- 18E975FD1EA4873F00083D49 /* Cocoa.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
81E2C20514C5684A004CE5C2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
81B8C53815108B89000C56DC /* Carbon.framework in Frameworks */,
81B8C535151078DB000C56DC /* WebKit.framework in Frameworks */,
- 81F81AED14D76D2400845D4C /* OpenGL.framework in Frameworks */,
81E2C20C14C5684A004CE5C2 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -67,8 +46,7 @@
8102525914C573EB0022296D /* Sources */ = {
isa = PBXGroup;
children = (
- 81F4B4D814C6888B001B4465 /* WebView.m */,
- 183C364F1EA488E50071D97B /* WebViewSeparated.m */,
+ 81F4B4D814C6888B001B4465 /* WebView.mm */,
);
name = Sources;
sourceTree = "";
@@ -87,7 +65,6 @@
isa = PBXGroup;
children = (
81E2C20814C5684A004CE5C2 /* WebView.bundle */,
- 18E976041EA4873F00083D49 /* WebViewSeparated.bundle */,
);
name = Products;
sourceTree = "";
@@ -120,24 +97,6 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
- 18E975F61EA4873F00083D49 /* WebViewSeparated */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 18E976011EA4873F00083D49 /* Build configuration list for PBXNativeTarget "WebViewSeparated" */;
- buildPhases = (
- 18E975F71EA4873F00083D49 /* Sources */,
- 18E975F91EA4873F00083D49 /* Frameworks */,
- 18E975FE1EA4873F00083D49 /* Resources */,
- 18E976001EA4873F00083D49 /* ShellScript */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = WebViewSeparated;
- productName = WebView;
- productReference = 18E976041EA4873F00083D49 /* WebViewSeparated.bundle */;
- productType = "com.apple.product-type.bundle";
- };
81E2C20714C5684A004CE5C2 /* WebView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 81E2C21A14C5684A004CE5C2 /* Build configuration list for PBXNativeTarget "WebView" */;
@@ -162,11 +121,11 @@
81E2C1FF14C5684A004CE5C2 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1000;
+ LastUpgradeCheck = 1200;
};
buildConfigurationList = 81E2C20214C5684A004CE5C2 /* Build configuration list for PBXProject "WebView" */;
compatibilityVersion = "Xcode 8.0";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -178,20 +137,11 @@
projectRoot = "";
targets = (
81E2C20714C5684A004CE5C2 /* WebView */,
- 18E975F61EA4873F00083D49 /* WebViewSeparated */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
- 18E975FE1EA4873F00083D49 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 18E975FF1EA4873F00083D49 /* InfoPlist.strings in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
81E2C20614C5684A004CE5C2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -203,19 +153,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 18E976001EA4873F00083D49 /* ShellScript */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "";
- };
81F4B4F514C696C4001B4465 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -232,63 +169,17 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
- 18E975F71EA4873F00083D49 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 183C36501EA488E50071D97B /* WebViewSeparated.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
81E2C20414C5684A004CE5C2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 81F4B4D914C6888B001B4465 /* WebView.m in Sources */,
+ 81F4B4D914C6888B001B4465 /* WebView.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
- 18E976021EA4873F00083D49 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CLANG_CXX_LIBRARY = "compiler-default";
- CLANG_ENABLE_OBJC_WEAK = YES;
- GCC_PRECOMPILE_PREFIX_HEADER = YES;
- GCC_PREFIX_HEADER = Resources/Prefix.pch;
- INFOPLIST_FILE = "Resources/Info-WebViewSeparated.plist";
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
- LD_RUNPATH_SEARCH_PATHS = "";
- LIBRARY_SEARCH_PATHS = "$(inherited)";
- MACOSX_DEPLOYMENT_TARGET = 10.10;
- PRODUCT_BUNDLE_IDENTIFIER = "net.gree.unitywebview.${PRODUCT_NAME:rfc1034identifier}";
- PRODUCT_NAME = "$(TARGET_NAME)";
- VALID_ARCHS = x86_64;
- WRAPPER_EXTENSION = bundle;
- };
- name = Debug;
- };
- 18E976031EA4873F00083D49 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CLANG_CXX_LIBRARY = "compiler-default";
- CLANG_ENABLE_OBJC_WEAK = YES;
- GCC_PRECOMPILE_PREFIX_HEADER = YES;
- GCC_PREFIX_HEADER = Resources/Prefix.pch;
- INFOPLIST_FILE = "Resources/Info-WebViewSeparated.plist";
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
- LD_RUNPATH_SEARCH_PATHS = "";
- LIBRARY_SEARCH_PATHS = "$(inherited)";
- MACOSX_DEPLOYMENT_TARGET = 10.10;
- PRODUCT_BUNDLE_IDENTIFIER = "net.gree.unitywebview.${PRODUCT_NAME:rfc1034identifier}";
- PRODUCT_NAME = "$(TARGET_NAME)";
- VALID_ARCHS = x86_64;
- WRAPPER_EXTENSION = bundle;
- };
- name = Release;
- };
81E2C21814C5684A004CE5C2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -306,6 +197,7 @@
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -333,12 +225,11 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "";
- MACOSX_DEPLOYMENT_TARGET = 10.6;
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "";
SDKROOT = macosx;
USE_HEADERMAP = YES;
- VALID_ARCHS = x86_64;
};
name = Debug;
};
@@ -359,6 +250,7 @@
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -379,30 +271,30 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "";
- MACOSX_DEPLOYMENT_TARGET = 10.6;
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
OTHER_LDFLAGS = "";
SDKROOT = macosx;
USE_HEADERMAP = YES;
- VALID_ARCHS = x86_64;
};
name = Release;
};
81E2C21B14C5684A004CE5C2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_WEAK = YES;
+ GCC_C_LANGUAGE_STANDARD = "compiler-default";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Resources/Prefix.pch;
INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_RUNPATH_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MACOSX_DEPLOYMENT_TARGET = 10.7;
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = "net.gree.unitywebview.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
USE_HEADERMAP = YES;
- VALID_ARCHS = x86_64;
WRAPPER_EXTENSION = bundle;
};
name = Debug;
@@ -410,19 +302,20 @@
81E2C21C14C5684A004CE5C2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_WEAK = YES;
+ GCC_C_LANGUAGE_STANDARD = "compiler-default";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Resources/Prefix.pch;
INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_RUNPATH_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MACOSX_DEPLOYMENT_TARGET = 10.7;
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = "net.gree.unitywebview.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
USE_HEADERMAP = YES;
- VALID_ARCHS = x86_64;
WRAPPER_EXTENSION = bundle;
};
name = Release;
@@ -430,15 +323,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
- 18E976011EA4873F00083D49 /* Build configuration list for PBXNativeTarget "WebViewSeparated" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 18E976021EA4873F00083D49 /* Debug */,
- 18E976031EA4873F00083D49 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
81E2C20214C5684A004CE5C2 /* Build configuration list for PBXProject "WebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/plugins/Mac/install.sh b/plugins/Mac/install.sh
index f4388dd3..0cc6674e 100755
--- a/plugins/Mac/install.sh
+++ b/plugins/Mac/install.sh
@@ -1,11 +1,9 @@
#!/bin/bash
DSTDIR="../../build/Packager/Assets/Plugins"
rm -rf DerivedData
-xcodebuild -target WebView -configuration Release -arch i386 -arch x86_64 build CONFIGURATION_BUILD_DIR='DerivedData' | xcpretty
-xcodebuild -target WebViewSeparated -configuration Release -arch x86_64 build CONFIGURATION_BUILD_DIR='DerivedData' | xcpretty
+xcodebuild -target WebView -configuration Release -arch x86_64 -arch arm64 build CONFIGURATION_BUILD_DIR='DerivedData' | xcbeautify
mkdir -p $DSTDIR
cp -r DerivedData/WebView.bundle $DSTDIR
-cp -r DerivedData/WebViewSeparated.bundle $DSTDIR
rm -rf DerivedData
cp *.bundle.meta $DSTDIR
diff --git a/plugins/WebGLTemplates/unity-webview-2020/index.html b/plugins/WebGLTemplates/unity-webview-2020/index.html
new file mode 100644
index 00000000..84f13023
--- /dev/null
+++ b/plugins/WebGLTemplates/unity-webview-2020/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Unity WebGL Player | {{{ PRODUCT_NAME }}}
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/WebGLTemplates/unity-webview-2020/unity-webview.js b/plugins/WebGLTemplates/unity-webview-2020/unity-webview.js
new file mode 100644
index 00000000..6a9c777f
--- /dev/null
+++ b/plugins/WebGLTemplates/unity-webview-2020/unity-webview.js
@@ -0,0 +1,103 @@
+var unityWebView =
+{
+ loaded: [],
+
+ init : function (name) {
+ $containers = $('.webviewContainer');
+ if ($containers.length === 0) {
+ $('')
+ .appendTo($('#unity-container'));
+ }
+ var $last = $('.webviewContainer:last');
+ var clonedTop = parseInt($last.css('top')) - 100;
+ var $clone = $last.clone().insertAfter($last).css('top', clonedTop + '%');
+ var $iframe =
+ $('')
+ .attr('id', 'webview_' + name)
+ .appendTo($last)
+ .on('load', function () {
+ $(this).attr('loaded', 'true');
+ var contents = $(this).contents();
+ var w = $(this)[0].contentWindow;
+ contents.find('a').click(function (e) {
+ var href = $.trim($(this).attr('href'));
+ if (href.substr(0, 6) === 'unity:') {
+ unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
+ e.preventDefault();
+ }
+ });
+
+ contents.find('form').submit(function () {
+ $this = $(this);
+ var action = $.trim($this.attr('action'));
+ if (action.substr(0, 6) === 'unity:') {
+ var message = action.substring(6, action.length);
+ if ($this.attr('method').toLowerCase() == 'get') {
+ message += '?' + $this.serialize();
+ }
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ return false;
+ }
+ return true;
+ });
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
+ });
+ },
+
+ sendMessage: function (name, message) {
+ unityInstance.SendMessage(name, "CallFromJS", message);
+ },
+
+ setMargins: function (name, left, top, right, bottom) {
+ var container = $('#unity-container');
+ var r = (container.hasClass('unity-desktop')) ? window.devicePixelRatio : 1;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
+ var canvas = $('#unity-canvas');
+ var w1 = canvas.attr('width');
+ var h1 = canvas.attr('height');
+
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w1 - left - right) / w0 * 100;
+ var hp = (h1 - top - bottom) / h0 * 100;
+
+ this.iframe(name)
+ .css('left', lp + '%')
+ .css('top', tp + '%')
+ .css('width', wp + '%')
+ .css('height', hp + '%');
+ },
+
+ setVisibility: function (name, visible) {
+ if (visible)
+ this.iframe(name).show();
+ else
+ this.iframe(name).hide();
+ },
+
+ loadURL: function(name, url) {
+ this.iframe(name).attr('loaded', 'false')[0].contentWindow.location.replace(url);
+ },
+
+ evaluateJS: function (name, js) {
+ $iframe = this.iframe(name);
+ if ($iframe.attr('loaded') === 'true') {
+ $iframe[0].contentWindow.eval(js);
+ } else {
+ $iframe.on('load', function(){
+ $(this)[0].contentWindow.eval(js);
+ });
+ }
+ },
+
+ destroy: function (name) {
+ this.iframe(name).parent().parent().remove();
+ },
+
+ iframe: function (name) {
+ return $('#webview_' + name);
+ },
+
+};
diff --git a/plugins/WebGLTemplates/unity-webview/index.html b/plugins/WebGLTemplates/unity-webview/index.html
index 5b5b3270..7474f1c4 100644
--- a/plugins/WebGLTemplates/unity-webview/index.html
+++ b/plugins/WebGLTemplates/unity-webview/index.html
@@ -20,7 +20,7 @@
diff --git a/plugins/WebGLTemplates/unity-webview/unity-webview.js b/plugins/WebGLTemplates/unity-webview/unity-webview.js
index e75fad94..64f473c0 100644
--- a/plugins/WebGLTemplates/unity-webview/unity-webview.js
+++ b/plugins/WebGLTemplates/unity-webview/unity-webview.js
@@ -6,7 +6,7 @@ var unityWebView =
$containers = $('.webviewContainer');
if ($containers.length === 0) {
$('')
- .appendTo($('#unityPlayer'));
+ .appendTo($('#gameContainer'));
}
var $last = $('.webviewContainer:last');
var clonedTop = parseInt($last.css('top')) - 100;
@@ -24,8 +24,6 @@ var unityWebView =
if (href.substr(0, 6) === 'unity:') {
unityInstance.SendMessage(name, "CallFromJS", href.substring(6, href.length));
e.preventDefault();
- } else {
- w.location.replace(href);
}
});
@@ -42,6 +40,8 @@ var unityWebView =
}
return true;
});
+
+ unityInstance.SendMessage(name, "CallOnLoaded", location.href);
});
},
@@ -50,14 +50,15 @@ var unityWebView =
},
setMargins: function (name, left, top, right, bottom) {
- var $player = $('#unityPlayer');
- var width = $player.width();
- var height = $player.height();
+ var container = $('#gameContainer');
+ var r = window.devicePixelRatio;
+ var w0 = container.width() * r;
+ var h0 = container.height() * r;
- var lp = left / width * 100;
- var tp = top / height * 100;
- var wp = (width - left - right) / width * 100;
- var hp = (height - top - bottom) / height * 100;
+ var lp = left / w0 * 100;
+ var tp = top / h0 * 100;
+ var wp = (w0 - left - right) / w0 * 100;
+ var hp = (h0 - top - bottom) / h0 * 100;
this.iframe(name)
.css('left', lp + '%')
diff --git a/plugins/WebViewObject.cs b/plugins/WebViewObject.cs
index 98872173..eddbcd51 100644
--- a/plugins/WebViewObject.cs
+++ b/plugins/WebViewObject.cs
@@ -27,926 +27,2187 @@
#if UNITY_2018_4_OR_NEWER
using UnityEngine.Networking;
#endif
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+using UnityEngine.InputSystem;
+#endif
+#endif
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
using System.IO;
using System.Text.RegularExpressions;
+using UnityEngine.EventSystems;
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+#endif
+#if UNITY_ANDROID
+using UnityEngine.Android;
#endif
using Callback = System.Action;
-#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
-public class UnitySendMessageDispatcher
+namespace Gree.UnityWebView
{
- public static void Dispatch(string name, string method, string message)
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ public class UnitySendMessageDispatcher
{
- GameObject obj = GameObject.Find(name);
- if (obj != null)
- obj.SendMessage(method, message);
+ public static void Dispatch(string name, string method, string message)
+ {
+ GameObject obj = GameObject.Find(name);
+ if (obj != null)
+ obj.SendMessage(method, message);
+ }
}
-}
#endif
-
-public class WebViewObject : MonoBehaviour
-{
- Callback onJS;
- Callback onError;
- Callback onHttpError;
- Callback onStarted;
- Callback onLoaded;
- bool visibility;
- int mMarginLeft;
- int mMarginTop;
- int mMarginRight;
- int mMarginBottom;
+
+ public class WebViewObject : MonoBehaviour
+ {
+ Callback onJS;
+ Callback onError;
+ Callback onHttpError;
+ Callback onStarted;
+ Callback onLoaded;
+ Callback onHooked;
+ Callback onCookies;
+ bool paused;
+ bool visibility;
+ bool alertDialogEnabled;
+ bool scrollBounceEnabled;
+ int mMarginLeft;
+ int mMarginTop;
+ int mMarginRight;
+ int mMarginBottom;
+ bool mMarginRelative;
+ float mMarginLeftComputed;
+ float mMarginTopComputed;
+ float mMarginRightComputed;
+ float mMarginBottomComputed;
+ bool mMarginRelativeComputed;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- IntPtr webView;
- Rect rect;
- Texture2D texture;
- string inputString;
- bool hasFocus;
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+#if UNITY_2018_2_OR_NEWER
+#else
+ byte[] textureDataBuffer;
+#endif
+ string inputString = "";
+ bool hasFocus;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ public GameObject canvas;
+ Image bg;
+ IntPtr webView;
+ Rect rect;
+ Texture2D texture;
+ byte[] textureDataBuffer;
+ string inputString = "";
+ bool hasFocus;
#elif UNITY_IPHONE
- IntPtr webView;
+ IntPtr webView;
#elif UNITY_ANDROID
- AndroidJavaObject webView;
+ AndroidJavaObject webView;
+
+ bool mVisibility;
+ int mKeyboardVisibleHeight;
+ float mResumedTimestamp;
+ int mLastScreenHeight;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ float androidNetworkReachabilityCheckT0 = -1.0f;
+ NetworkReachability? androidNetworkReachability0 = null;
+#endif
+ bool mAllowVideoCapture;
+ bool mAllowAudioCapture;
+
+ void OnApplicationPause(bool paused)
+ {
+ this.paused = paused;
+ if (webView == null)
+ return;
+ // if (!paused && mKeyboardVisibleHeight > 0)
+ // {
+ // webView.Call("SetVisibility", false);
+ // mResumedTimestamp = Time.realtimeSinceStartup;
+ // }
+ webView.Call("OnApplicationPause", paused);
+ }
- bool mVisibility;
- bool mIsKeyboardVisible0;
- bool mIsKeyboardVisible;
- float mResumedTimestamp;
+ void Update()
+ {
+ // NOTE:
+ //
+ // When OnApplicationPause(true) is called and the app is in closing, webView.Call(...)
+ // after that could cause crashes because underlying java instances were closed.
+ //
+ // This has not been cleary confirmed yet. However, as Update() is called once after
+ // OnApplicationPause(true), it is likely correct.
+ //
+ // Base on this assumption, we do nothing here if the app is paused.
+ //
+ // cf. https://github.com/gree/unity-webview/issues/991#issuecomment-1776628648
+ // cf. https://docs.unity3d.com/2020.3/Documentation/Manual/ExecutionOrder.html
+ //
+ // In between frames
+ //
+ // * OnApplicationPause: This is called at the end of the frame where the pause is detected,
+ // effectively between the normal frame updates. One extra frame will be issued after
+ // OnApplicationPause is called to allow the game to show graphics that indicate the
+ // paused state.
+ //
+ if (paused)
+ return;
+ if (webView == null)
+ return;
+#if UNITYWEBVIEW_ANDROID_ENABLE_NAVIGATOR_ONLINE
+ var t = Time.time;
+ if (t - 1.0f >= androidNetworkReachabilityCheckT0)
+ {
+ androidNetworkReachabilityCheckT0 = t;
+ var androidNetworkReachability = Application.internetReachability;
+ if (androidNetworkReachability0 != androidNetworkReachability)
+ {
+ androidNetworkReachability0 = androidNetworkReachability;
+ webView.Call("SetNetworkAvailable", androidNetworkReachability != NetworkReachability.NotReachable);
+ }
+ }
+#endif
+ if (mResumedTimestamp != 0.0f && Time.realtimeSinceStartup - mResumedTimestamp > 0.5f)
+ {
+ mResumedTimestamp = 0.0f;
+ webView.Call("SetVisibility", mVisibility);
+ }
+ if (Screen.height != mLastScreenHeight)
+ {
+ mLastScreenHeight = Screen.height;
+ webView.Call("EvaluateJS", "(function() {var e = document.activeElement; if (e != null && e.tagName.toLowerCase() != 'body') {e.blur(); e.focus();}})()");
+ }
+ for (;;) {
+ if (webView == null)
+ break;
+ var s = webView.Call("GetMessage");
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ case "SetKeyboardVisible":
+ SetKeyboardVisible(s.Substring(i + 1));
+ break;
+ case "RequestFileChooserPermissions":
+ RequestFileChooserPermissions();
+ break;
+ }
+ }
+ }
- void OnApplicationPause(bool paused)
- {
- if (webView == null)
- return;
- if (!paused)
+ /// Called from Java native plugin to set when the keyboard is opened
+ public void SetKeyboardVisible(string keyboardVisibleHeight)
{
- webView.Call("SetVisibility", false);
- mResumedTimestamp = Time.realtimeSinceStartup;
+ if (BottomAdjustmentDisabled())
+ {
+ return;
+ }
+ var keyboardVisibleHeight0 = mKeyboardVisibleHeight;
+ var keyboardVisibleHeight1 = Int32.Parse(keyboardVisibleHeight);
+ if (keyboardVisibleHeight0 != keyboardVisibleHeight1)
+ {
+ mKeyboardVisibleHeight = keyboardVisibleHeight1;
+ SetMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom, mMarginRelative);
+ EvaluateJS("setTimeout(function(){if(document&&document.activeElement){document.activeElement.scrollIntoView();}}, 200);");
+ }
}
- webView.Call("OnApplicationPause", paused);
- }
-
- void Update()
- {
- if (webView == null)
- return;
- if (mResumedTimestamp != 0.0f && Time.realtimeSinceStartup - mResumedTimestamp > 0.5f)
+
+ /// Called from Java native plugin to request permissions for the file chooser.
+ public void RequestFileChooserPermissions()
{
- mResumedTimestamp = 0.0f;
- webView.Call("SetVisibility", mVisibility);
+ var permissions = new List();
+ using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
+ {
+ if (version.GetStatic("SDK_INT") >= 33)
+ {
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_IMAGES");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_VIDEO");
+ }
+ if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO"))
+ {
+ permissions.Add("android.permission.READ_MEDIA_AUDIO");
+ }
+ }
+ else
+ {
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
+ {
+ permissions.Add(Permission.ExternalStorageRead);
+ }
+ if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
+ {
+ permissions.Add(Permission.ExternalStorageWrite);
+ }
+ }
+ }
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && mAllowVideoCapture)
+ {
+ permissions.Add(Permission.Camera);
+ }
+#endif
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && mAllowAudioCapture)
+ {
+ permissions.Add(Permission.Microphone);
+ }
+#endif
+ if (permissions.Count > 0)
+ {
+#if UNITY_2020_2_OR_NEWER
+ var grantedCount = 0;
+ var deniedCount = 0;
+ var callbacks = new PermissionCallbacks();
+ callbacks.PermissionGranted += (permission) =>
+ {
+ grantedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDenied += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ callbacks.PermissionDeniedAndDontAskAgain += (permission) =>
+ {
+ deniedCount++;
+ if (grantedCount + deniedCount == permissions.Count)
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count));
+ }
+ };
+ Permission.RequestUserPermissions(permissions.ToArray(), callbacks);
+#else
+ StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray()));
+#endif
+ }
+ else
+ {
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(true));
+ }
}
- }
-
- /// Called from Java native plugin to set when the keyboard is opened
- public void SetKeyboardVisible(string pIsVisible)
- {
- mIsKeyboardVisible = (pIsVisible == "true");
- if (mIsKeyboardVisible != mIsKeyboardVisible0)
+
+#if UNITY_2020_2_OR_NEWER
+#else
+ int mRequestPermissionPhase;
+
+ IEnumerator RequestFileChooserPermissionsCoroutine(string[] permissions)
+ {
+ foreach (var permission in permissions)
+ {
+ mRequestPermissionPhase = 0;
+ Permission.RequestUserPermission(permission);
+ // waiting permission dialog that may not be opened.
+ for (var i = 0; i < 8 && mRequestPermissionPhase == 0; i++)
+ {
+ yield return new WaitForSeconds(0.25f);
+ }
+ if (mRequestPermissionPhase == 0)
+ {
+ // permission dialog was not opened.
+ continue;
+ }
+ while (mRequestPermissionPhase == 1)
+ {
+ yield return new WaitForSeconds(0.3f);
+ }
+ }
+ yield return new WaitForSeconds(0.3f);
+ var granted = 0;
+ foreach (var permission in permissions)
+ {
+ if (Permission.HasUserAuthorizedPermission(permission))
+ {
+ granted++;
+ }
+ }
+ StartCoroutine(CallOnRequestFileChooserPermissionsResult(granted == permissions.Length));
+ }
+
+ void OnApplicationFocus(bool hasFocus)
{
- mIsKeyboardVisible0 = mIsKeyboardVisible;
- SetMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom);
+ if (hasFocus)
+ {
+ if (mRequestPermissionPhase == 1)
+ {
+ mRequestPermissionPhase = 2;
+ }
+ }
+ else
+ {
+ if (mRequestPermissionPhase == 0)
+ {
+ mRequestPermissionPhase = 1;
+ }
+ }
}
- }
+#endif
- public int AdjustBottomMargin(int bottom)
- {
- if (!mIsKeyboardVisible)
+ private IEnumerator CallOnRequestFileChooserPermissionsResult(bool granted)
{
- return bottom;
+ for (var i = 0; i < 3; i++)
+ {
+ yield return null;
+ }
+ webView.Call("OnRequestFileChooserPermissionsResult", granted);
}
- else
+
+ public int AdjustBottomMargin(int bottom)
{
- int keyboardHeight = 0;
- using(AndroidJavaClass UnityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ if (BottomAdjustmentDisabled())
+ {
+ return bottom;
+ }
+ else if (mKeyboardVisibleHeight <= 0)
{
- AndroidJavaObject View = UnityClass.GetStatic("currentActivity").Get("mUnityPlayer").Call("getView");
- using(AndroidJavaObject Rct = new AndroidJavaObject("android.graphics.Rect"))
+ return bottom;
+ }
+ else
+ {
+ int keyboardHeight = 0;
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
{
- View.Call("getWindowVisibleDisplayFrame", Rct);
- keyboardHeight = View.Call("getHeight") - Rct.Call("height");
+ if (view.Call("getGlobalVisibleRect", rect))
+ {
+ int h0 = rect.Get("bottom");
+ view.Call("getWindowVisibleDisplayFrame", rect);
+ int h1 = rect.Get("bottom");
+ keyboardHeight = h0 - h1;
+ }
}
+ return (bottom > keyboardHeight) ? bottom : keyboardHeight;
}
- return (bottom > keyboardHeight) ? bottom : keyboardHeight;
}
- }
+
+ private bool BottomAdjustmentDisabled()
+ {
+#if UNITYWEBVIEW_ANDROID_FORCE_MARGIN_ADJUSTMENT_FOR_KEYBOARD
+ return false;
#else
- IntPtr webView;
+ return
+ !Screen.fullScreen
+ || ((Screen.autorotateToLandscapeLeft || Screen.autorotateToLandscapeRight)
+ && (Screen.autorotateToPortrait || Screen.autorotateToPortraitUpsideDown));
#endif
-
- public bool IsKeyboardVisible
- {
- get
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ IntPtr webView;
+#else
+ IntPtr webView;
+#endif
+
+ void Awake()
{
+ alertDialogEnabled = true;
+ scrollBounceEnabled = true;
+ mMarginLeftComputed = -9999;
+ mMarginTopComputed = -9999;
+ mMarginRightComputed = -9999;
+ mMarginBottomComputed = -9999;
+ }
+
+ public bool IsKeyboardVisible
+ {
+ get
+ {
#if !UNITY_EDITOR && UNITY_ANDROID
- return mIsKeyboardVisible;
+ return mKeyboardVisibleHeight > 0;
#elif !UNITY_EDITOR && UNITY_IPHONE
- return TouchScreenKeyboard.visible;
+ return TouchScreenKeyboard.visible;
#else
- return false;
+ return false;
#endif
+ }
+ }
+
+#if UNITY_EDITOR
+#if ENABLE_INPUT_SYSTEM
+ void OnEnable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput += OnTextInput;
+ }
}
- }
-#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
-#if WEBVIEW_SEPARATED
- [DllImport("WebViewSeparated")]
- private static extern string _CWebViewPlugin_GetAppPath();
- [DllImport("WebViewSeparated")]
- private static extern IntPtr _CWebViewPlugin_Init(
- string gameObject, bool transparent, int width, int height, string ua, bool ineditor);
- [DllImport("WebViewSeparated")]
- private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_SetRect(
- IntPtr instance, int width, int height);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_SetVisibility(
- IntPtr instance, bool visibility);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_LoadURL(
- IntPtr instance, string url);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_LoadHTML(
- IntPtr instance, string html, string baseUrl);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_EvaluateJS(
- IntPtr instance, string url);
- [DllImport("WebViewSeparated")]
- private static extern int _CWebViewPlugin_Progress(
- IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern bool _CWebViewPlugin_CanGoBack(
- IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern bool _CWebViewPlugin_CanGoForward(
- IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_GoBack(
- IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_GoForward(
- IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_Update(IntPtr instance,
- int x, int y, float deltaY, bool down, bool press, bool release,
- bool keyPress, short keyCode, string keyChars,
- bool refreshBitmap);
- [DllImport("WebViewSeparated")]
- private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_SetTextureId(IntPtr instance, int textureId);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_SetCurrentInstance(IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern IntPtr GetRenderEventFunc();
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
- [DllImport("WebViewSeparated")]
- private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
- [DllImport("WebViewSeparated")]
- private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
- [DllImport("WebViewSeparated")]
- private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
-#else
- [DllImport("WebView")]
- private static extern string _CWebViewPlugin_GetAppPath();
- [DllImport("WebView")]
- private static extern IntPtr _CWebViewPlugin_Init(
- string gameObject, bool transparent, int width, int height, string ua, bool ineditor);
- [DllImport("WebView")]
- private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_SetRect(
- IntPtr instance, int width, int height);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_SetVisibility(
- IntPtr instance, bool visibility);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_LoadURL(
- IntPtr instance, string url);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_LoadHTML(
- IntPtr instance, string html, string baseUrl);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_EvaluateJS(
- IntPtr instance, string url);
- [DllImport("WebView")]
- private static extern int _CWebViewPlugin_Progress(
- IntPtr instance);
- [DllImport("WebView")]
- private static extern bool _CWebViewPlugin_CanGoBack(
- IntPtr instance);
- [DllImport("WebView")]
- private static extern bool _CWebViewPlugin_CanGoForward(
- IntPtr instance);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_GoBack(
- IntPtr instance);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_GoForward(
- IntPtr instance);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_Update(IntPtr instance,
- int x, int y, float deltaY, bool down, bool press, bool release,
- bool keyPress, short keyCode, string keyChars,
- bool refreshBitmap);
- [DllImport("WebView")]
- private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
- [DllImport("WebView")]
- private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_SetTextureId(IntPtr instance, int textureId);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_SetCurrentInstance(IntPtr instance);
- [DllImport("WebView")]
- private static extern IntPtr GetRenderEventFunc();
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
- [DllImport("WebView")]
- private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
- [DllImport("WebView")]
- private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
- [DllImport("WebView")]
- private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+ void OnDisable()
+ {
+ if (Keyboard.current != null)
+ {
+ Keyboard.current.onTextInput -= OnTextInput;
+ }
+ }
+
+ void OnTextInput(char ch)
+ {
+ if (hasFocus)
+ {
+ inputString += ch;
+ }
+ }
+#endif
#endif
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_InitStatic(
+ bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(
+ string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(
+ IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_BitmapARGB(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetAppPath();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_InitStatic(bool inEditor, bool useMetal);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_IsInitialized(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, int width, int height, string ua, bool separated);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetRect(IntPtr instance, int width, int height);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SetVisibility(IntPtr instance, bool visibility);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadURL(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_LoadHTML(IntPtr instance, string html, string baseUrl);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_EvaluateJS(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_Progress(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern bool _CWebViewPlugin_CanGoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoBack(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GoForward(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Reload(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendMouseEvent(IntPtr instance, int x, int y, float deltaY, int mouseState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SendKeyEvent(IntPtr instance, int x, int y, string keyChars, ushort keyCode, int keyState);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Update(IntPtr instance, bool refreshBitmap, int devicePixelRatio);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_Render(IntPtr instance, IntPtr textureBuffer);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("WebView")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("WebView")]
+ private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
#elif UNITY_IPHONE
- [DllImport("__Internal")]
- private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, string ua, bool enableWKWebView);
- [DllImport("__Internal")]
- private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_SetMargins(
- IntPtr instance, float left, float top, float right, float bottom, bool relative);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_SetVisibility(
- IntPtr instance, bool visibility);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_LoadURL(
- IntPtr instance, string url);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_LoadHTML(
- IntPtr instance, string html, string baseUrl);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_EvaluateJS(
- IntPtr instance, string url);
- [DllImport("__Internal")]
- private static extern int _CWebViewPlugin_Progress(
- IntPtr instance);
- [DllImport("__Internal")]
- private static extern bool _CWebViewPlugin_CanGoBack(
- IntPtr instance);
- [DllImport("__Internal")]
- private static extern bool _CWebViewPlugin_CanGoForward(
- IntPtr instance);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_GoBack(
- IntPtr instance);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_GoForward(
- IntPtr instance);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
- [DllImport("__Internal")]
- private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
- [DllImport("__Internal")]
- private static extern void _CWebViewPlugin_ClearCookies();
- [DllImport("__Internal")]
- private static extern string _CWebViewPlugin_GetCookies(string url);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_IsInitialized(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, bool zoom, string ua, bool enableWKWebView, int wkContentMode, bool wkAllowsLinkPreview, bool wkAllowsBackForwardNavigationGestures, int radius);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetMargins(
+ IntPtr instance, float left, float top, float right, float bottom, bool relative);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollbarsVisibility(
+ IntPtr instance, bool visibility);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetAlertDialogEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetScrollBounceEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetInteractionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetGoogleAppRedirectionEnabled(
+ IntPtr instance, bool enabled);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_SetURLPattern(
+ IntPtr instance, string allowPattern, string denyPattern, string hookPattern);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadURL(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_LoadHTML(
+ IntPtr instance, string html, string baseUrl);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_EvaluateJS(
+ IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern int _CWebViewPlugin_Progress(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern bool _CWebViewPlugin_CanGoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoBack(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GoForward(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_Reload(
+ IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
+ [DllImport("__Internal")]
+ private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookie(string url, string name);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SaveCookies();
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_GetCookies(IntPtr instance, string url);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetBasicAuthInfo(IntPtr instance, string userName, string password);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_ClearCache(IntPtr instance, bool includeDiskFiles);
+ [DllImport("__Internal")]
+ private static extern void _CWebViewPlugin_SetSuspended(IntPtr instance, bool suspended);
#elif UNITY_WEBGL
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_init(string name);
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_setMargins(string name, int left, int top, int right, int bottom);
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_setVisibility(string name, bool visible);
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_loadURL(string name, string url);
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_evaluateJS(string name, string js);
- [DllImport("__Internal")]
- private static extern void _gree_unity_webview_destroy(string name);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_init(string name);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setMargins(string name, int left, int top, int right, int bottom);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_setVisibility(string name, bool visible);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_loadURL(string name, string url);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_evaluateJS(string name, string js);
+ [DllImport("__Internal")]
+ private static extern void _gree_unity_webview_destroy(string name);
#endif
-
- public static bool IsWebViewAvailable()
- {
+
+ public static bool IsWebViewAvailable()
+ {
#if !UNITY_EDITOR && UNITY_ANDROID
- return (new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin")).CallStatic("IsWebViewAvailable");
+ using (var plugin = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin"))
+ {
+ return plugin.CallStatic("IsWebViewAvailable");
+ }
#else
- return true;
+ return true;
#endif
- }
-
- public void Init(
- Callback cb = null,
- bool transparent = false,
- string ua = "",
- Callback err = null,
- Callback httpErr = null,
- Callback ld = null,
- bool enableWKWebView = false,
- Callback started = null)
- {
- onJS = cb;
- onError = err;
- onHttpError = httpErr;
- onStarted = started;
- onLoaded = ld;
+ }
+
+ public bool IsInitialized()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return true;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_IsInitialized(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Call("IsInitialized");
+#endif
+ }
+
+ public void Init(
+ Callback cb = null,
+ Callback err = null,
+ Callback httpErr = null,
+ Callback ld = null,
+ Callback started = null,
+ Callback hooked = null,
+ Callback cookies = null,
+ bool transparent = false,
+ bool zoom = true,
+ string ua = "",
+ int radius = 0,
+ // android
+ int androidForceDarkMode = 0, // 0: follow system setting, 1: force dark off, 2: force dark on
+ // ios
+ bool enableWKWebView = true,
+ int wkContentMode = 0, // 0: recommended, 1: mobile, 2: desktop
+ bool wkAllowsLinkPreview = true,
+ bool wkAllowsBackForwardNavigationGestures = true,
+ // editor
+ bool separated = false)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.OSXEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal);
+#endif
+ onJS = cb;
+ onError = err;
+ onHttpError = httpErr;
+ onStarted = started;
+ onLoaded = ld;
+ onHooked = hooked;
+ onCookies = cookies;
#if UNITY_WEBGL
#if !UNITY_EDITOR
- _gree_unity_webview_init(name);
+ _gree_unity_webview_init(name);
#endif
#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.init", name);
+ Application.ExternalCall("unityWebView.init", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ Debug.LogError("Webview is not supported on this platform.");
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- Debug.LogError("Webview is not supported on this platform.");
+ _CWebViewPlugin_InitStatic(
+ Application.platform == RuntimePlatform.WindowsEditor,
+ SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11);
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- {
- var uri = new Uri(_CWebViewPlugin_GetAppPath());
- var info = File.ReadAllText(uri.LocalPath + "Contents/Info.plist");
- if (Regex.IsMatch(info, @"CFBundleGetInfoString\s*Unity version [5-9]\.[3-9]")
- && !Regex.IsMatch(info, @"NSAppTransportSecurity\s*\s*NSAllowsArbitraryLoads\s*\s*")) {
- Debug.LogWarning("WebViewObject: NSAppTransportSecurity isn't configured to allow HTTP. If you need to allow any HTTP access, please shutdown Unity and invoke:\n/usr/libexec/PlistBuddy -c \"Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true\" /Applications/Unity/Unity.app/Contents/Info.plist");
+ {
+ var uri = new Uri(_CWebViewPlugin_GetAppPath());
+ var info = File.ReadAllText(uri.LocalPath + "Contents/Info.plist");
+ if (Regex.IsMatch(info, @"CFBundleGetInfoString\s*Unity version [5-9]\.[3-9]")
+ && !Regex.IsMatch(info, @"NSAppTransportSecurity\s*\s*NSAllowsArbitraryLoads\s*\s*")) {
+ Debug.LogWarning("WebViewObject: NSAppTransportSecurity isn't configured to allow HTTP. If you need to allow any HTTP access, please shutdown Unity and invoke:\n/usr/libexec/PlistBuddy -c \"Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true\" /Applications/Unity/Unity.app/Contents/Info.plist");
+ }
}
- }
#if UNITY_EDITOR_OSX
- // if (string.IsNullOrEmpty(ua)) {
- // ua = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
- // }
-#endif
- webView = _CWebViewPlugin_Init(
- name,
- transparent,
- Screen.width,
- Screen.height,
- ua,
- Application.platform == RuntimePlatform.OSXEditor);
- // define pseudo requestAnimationFrame.
- EvaluateJS(@"(function() {
- var vsync = 1000 / 60;
- var t0 = window.performance.now();
- window.requestAnimationFrame = function(callback, element) {
- var t1 = window.performance.now();
- var duration = t1 - t0;
- var d = vsync - ((duration > vsync) ? duration % vsync : duration);
- var id = window.setTimeout(function() {t0 = window.performance.now(); callback(t1 + d);}, d);
- return id;
- };
- })()");
- rect = new Rect(0, 0, Screen.width, Screen.height);
- OnApplicationFocus(true);
+ // if (string.IsNullOrEmpty(ua)) {
+ // ua = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
+ // }
+#endif
+ webView = _CWebViewPlugin_Init(
+ name,
+ transparent,
+ zoom,
+ Screen.width,
+ Screen.height,
+ ua
+#if UNITY_EDITOR
+ , separated
+#else
+ , false
+#endif
+ );
+ rect = new Rect(0, 0, Screen.width, Screen.height);
#elif UNITY_IPHONE
- webView = _CWebViewPlugin_Init(name, transparent, ua, enableWKWebView);
+ webView = _CWebViewPlugin_Init(name, transparent, zoom, ua, enableWKWebView, wkContentMode, wkAllowsLinkPreview, wkAllowsBackForwardNavigationGestures, radius);
#elif UNITY_ANDROID
- webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin");
- webView.Call("Init", name, transparent, ua);
+ webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin");
+#if UNITY_2021_1_OR_NEWER
+ webView.SetStatic("forceBringToFront", true);
+#endif
+ webView.Call("Init", name, transparent, zoom, androidForceDarkMode, ua, radius);
#else
- Debug.LogError("Webview is not supported on this platform.");
+ Debug.LogError("Webview is not supported on this platform.");
#endif
- }
-
- protected virtual void OnDestroy()
- {
+ }
+
+ protected virtual void OnDestroy()
+ {
#if UNITY_WEBGL
#if !UNITY_EDITOR
- _gree_unity_webview_destroy(name);
+ _gree_unity_webview_destroy(name);
#endif
#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.destroy", name);
+ Application.ExternalCall("unityWebView.destroy", name);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (bg != null)
+ {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ var ptr = webView;
+ webView = IntPtr.Zero;
+ _CWebViewPlugin_Destroy(ptr);
+ Destroy(texture);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_Destroy(webView);
- webView = IntPtr.Zero;
- Destroy(texture);
+ if (bg != null) {
+ Destroy(bg.gameObject);
+ }
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
+ Destroy(texture);
#elif UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_Destroy(webView);
- webView = IntPtr.Zero;
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Destroy(webView);
+ webView = IntPtr.Zero;
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("Destroy");
- webView = null;
+ if (webView == null)
+ return;
+ webView.Call("Destroy");
+ webView.Dispose();
+ webView = null;
#endif
- }
-
- // Use this function instead of SetMargins to easily set up a centered window
- // NOTE: for historical reasons, `center` means the lower left corner and positive y values extend up.
- public void SetCenterPositionWithScale(Vector2 center, Vector2 scale)
- {
+ }
+
+ public void Pause()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
-#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
-#else
- float left = (Screen.width - scale.x) / 2.0f + center.x;
- float right = Screen.width - (left + scale.x);
- float bottom = (Screen.height - scale.y) / 2.0f + center.y;
- float top = Screen.height - (bottom + scale.y);
- SetMargins((int)left, (int)top, (int)right, (int)bottom);
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ // NOTE: this suspends media playback only.
+ if (webView == null)
+ return;
+ _CWebViewPlugin_SetSuspended(webView, true);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("Pause");
#endif
- }
-
- public void SetMargins(int left, int top, int right, int bottom, bool relative = false)
- {
+ }
+
+ public void Resume()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
-#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- if (webView == IntPtr.Zero)
- return;
+ //TODO: UNSUPPORTED
#elif UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
+ // NOTE: this resumes media playback only.
+ _CWebViewPlugin_SetSuspended(webView, false);
#elif UNITY_ANDROID
- if (webView == null)
- return;
+ if (webView == null)
+ return;
+ webView.Call("Resume");
#endif
- mMarginLeft = left;
- mMarginTop = top;
- mMarginRight = right;
- mMarginBottom = bottom;
-#if UNITY_WEBGL
-#if !UNITY_EDITOR
- _gree_unity_webview_setMargins(name, left, top, right, bottom);
+ }
+
+ // Use this function instead of SetMargins to easily set up a centered window
+ // NOTE: for historical reasons, `center` means the lower left corner and positive y values extend up.
+ public void SetCenterPositionWithScale(Vector2 center, Vector2 scale)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
+#else
+ float left = (Screen.width - scale.x) / 2.0f + center.x;
+ float right = Screen.width - (left + scale.x);
+ float bottom = (Screen.height - scale.y) / 2.0f + center.y;
+ float top = Screen.height - (bottom + scale.y);
+ SetMargins((int)left, (int)top, (int)right, (int)bottom);
#endif
-#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.setMargins", name, left, top, right, bottom);
+ }
+
+ public void SetMargins(int left, int top, int right, int bottom, bool relative = false)
+ {
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- int width = Screen.width - (left + right);
- int height = Screen.height - (bottom + top);
- _CWebViewPlugin_SetRect(webView, width, height);
- rect = new Rect(left, bottom, width, height);
+ if (webView == IntPtr.Zero)
+ return;
#elif UNITY_IPHONE
- if (relative) {
- float w = (float)Screen.width;
- float h = (float)Screen.height;
- _CWebViewPlugin_SetMargins(webView, left / w, top / h, right / w, bottom / h, true);
- } else {
- _CWebViewPlugin_SetMargins(webView, (float)left, (float)top, (float)right, (float)bottom, false);
- }
+ if (webView == IntPtr.Zero)
+ return;
#elif UNITY_ANDROID
- if (relative) {
- float w = (float)Screen.width;
- float h = (float)Screen.height;
- int iw = Screen.currentResolution.width;
- int ih = Screen.currentResolution.height;
- webView.Call("SetMargins", (int)(left / w * iw), (int)(top / h * ih), (int)(right / w * iw), AdjustBottomMargin((int)(bottom / h * ih)));
- } else {
- webView.Call("SetMargins", left, top, right, AdjustBottomMargin(bottom));
- }
+ if (webView == null)
+ return;
#endif
- }
-
- public void SetVisibility(bool v)
- {
-#if UNITY_WEBGL
-#if !UNITY_EDITOR
- _gree_unity_webview_setVisibility(name, v);
+
+ mMarginLeft = left;
+ mMarginTop = top;
+ mMarginRight = right;
+ mMarginBottom = bottom;
+ mMarginRelative = relative;
+ float ml, mt, mr, mb;
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_WEBPLAYER || UNITY_WEBGL
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+#elif UNITY_IPHONE
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ ml = left / w;
+ mt = top / h;
+ mr = right / w;
+ mb = bottom / h;
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = bottom;
+ }
+#elif UNITY_ANDROID
+ if (relative)
+ {
+ float w = (float)Screen.width;
+ float h = (float)Screen.height;
+ int iw = Display.main.systemWidth;
+ int ih = Display.main.systemHeight;
+ if (!Screen.fullScreen)
+ {
+ using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
+ using (var activity = unityClass.GetStatic("currentActivity"))
+ using (var player = activity.Get("mUnityPlayer"))
+ using (var view = player.Call("getView"))
+ using (var rect = new AndroidJavaObject("android.graphics.Rect"))
+ {
+ view.Call("getDrawingRect", rect);
+ iw = rect.Call("width");
+ ih = rect.Call("height");
+ }
+ }
+ ml = left / w * iw;
+ mt = top / h * ih;
+ mr = right / w * iw;
+ mb = AdjustBottomMargin((int)(bottom / h * ih));
+ }
+ else
+ {
+ ml = left;
+ mt = top;
+ mr = right;
+ mb = AdjustBottomMargin(bottom);
+ }
#endif
-#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.setVisibility", name, v);
+ bool r = relative;
+
+ if (ml == mMarginLeftComputed
+ && mt == mMarginTopComputed
+ && mr == mMarginRightComputed
+ && mb == mMarginBottomComputed
+ && r == mMarginRelativeComputed)
+ {
+ return;
+ }
+ mMarginLeftComputed = ml;
+ mMarginTopComputed = mt;
+ mMarginRightComputed = mr;
+ mMarginBottomComputed = mb;
+ mMarginRelativeComputed = r;
+
+#if UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.setMargins", name, (int)ml, (int)mt, (int)mr, (int)mb);
+#elif UNITY_WEBGL && !UNITY_EDITOR
+ _gree_unity_webview_setMargins(name, (int)ml, (int)mt, (int)mr, (int)mb);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_SetVisibility(webView, v);
+ int width = (int)(Screen.width - (ml + mr));
+ int height = (int)(Screen.height - (mb + mt));
+ _CWebViewPlugin_SetRect(webView, width, height);
+ rect = new Rect(left, bottom, width, height);
+ UpdateBGTransform();
#elif UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_SetVisibility(webView, v);
+ _CWebViewPlugin_SetMargins(webView, ml, mt, mr, mb, r);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- mVisibility = v;
- webView.Call("SetVisibility", v);
+ webView.Call("SetMargins", (int)ml, (int)mt, (int)mr, (int)mb);
#endif
- visibility = v;
- }
-
- public bool GetVisibility()
- {
- return visibility;
- }
-
- public void LoadURL(string url)
- {
- if (string.IsNullOrEmpty(url))
- return;
+ }
+
+ public void SetVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (bg != null)
+ {
+ bg.gameObject.SetActive(v);
+ }
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#endif
+ if (GetVisibility() && !v)
+ {
+ EvaluateJS("if (document && document.activeElement) document.activeElement.blur();");
+ }
#if UNITY_WEBGL
#if !UNITY_EDITOR
- _gree_unity_webview_loadURL(name, url);
+ _gree_unity_webview_setVisibility(name, v);
#endif
#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.loadURL", name, url);
+ Application.ExternalCall("unityWebView.setVisibility", name, v);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
-#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_LoadURL(webView, url);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetVisibility(webView, v);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("LoadURL", url);
+ if (webView == null)
+ return;
+ mVisibility = v;
+ webView.Call("SetVisibility", v);
#endif
- }
-
- public void LoadHTML(string html, string baseUrl)
- {
- if (string.IsNullOrEmpty(html))
- return;
- if (string.IsNullOrEmpty(baseUrl))
- baseUrl = "";
+ visibility = v;
+ }
+
+ public bool GetVisibility()
+ {
+ return visibility;
+ }
+
+ public void SetScrollbarsVisibility(bool v)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollbarsVisibility(webView, v);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetScrollbarsVisibility", v);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetInteractionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetInteractionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetInteractionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetGoogleAppRedirectionEnabled(bool enabled)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetGoogleAppRedirectionEnabled(webView, enabled);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetGoogleAppRedirectionEnabled", enabled);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetAlertDialogEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetAlertDialogEnabled(webView, e);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetAlertDialogEnabled", e);
+#else
+ // TODO: UNSUPPORTED
+#endif
+ alertDialogEnabled = e;
+ }
+
+ public bool GetAlertDialogEnabled()
+ {
+ return alertDialogEnabled;
+ }
+
+ public void SetScrollBounceEnabled(bool e)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetScrollBounceEnabled(webView, e);
+#elif UNITY_ANDROID
+ // TODO: UNSUPPORTED
+#else
+ // TODO: UNSUPPORTED
+#endif
+ scrollBounceEnabled = e;
+ }
+
+ public bool GetScrollBounceEnabled()
+ {
+ return scrollBounceEnabled;
+ }
+
+ public void SetCameraAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
+ if (webView == null)
+ return;
+ webView.Call("SetCameraAccess", allowed);
+ mAllowVideoCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetMicrophoneAccess(bool allowed)
+ {
+#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ // TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ // TODO: WebView2 not implemented in native plugin
+#elif UNITY_IPHONE
+ // TODO: UNSUPPORTED
+#elif UNITY_ANDROID
+#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
+ if (webView == null)
+ return;
+ webView.Call("SetMicrophoneAccess", allowed);
+ mAllowAudioCapture = allowed;
+#endif
+#else
+ // TODO: UNSUPPORTED
+#endif
+ }
+
+ public bool SetURLPattern(string allowPattern, string denyPattern, string hookPattern)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_SetURLPattern(webView, allowPattern, denyPattern, hookPattern);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("LoadHTML", html, baseUrl);
+ if (webView == null)
+ return false;
+ return webView.Call("SetURLPattern", allowPattern, denyPattern, hookPattern);
#endif
- }
-
- public void EvaluateJS(string js)
- {
+ }
+
+ public void LoadURL(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ return;
#if UNITY_WEBGL
#if !UNITY_EDITOR
- _gree_unity_webview_evaluateJS(name, js);
+ _gree_unity_webview_loadURL(name, url);
#endif
#elif UNITY_WEBPLAYER
- Application.ExternalCall("unityWebView.evaluateJS", name, js);
+ Application.ExternalCall("unityWebView.loadURL", name, url);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_EvaluateJS(webView, js);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadURL(webView, url);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("EvaluateJS", js);
+ if (webView == null)
+ return;
+ webView.Call("LoadURL", url);
#endif
- }
-
- public int Progress()
- {
+ }
+
+ public void LoadHTML(string html, string baseUrl)
+ {
+ if (string.IsNullOrEmpty(html))
+ return;
+ if (string.IsNullOrEmpty(baseUrl))
+ baseUrl = "";
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
- return 0;
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- return 0;
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return 0;
- return _CWebViewPlugin_Progress(webView);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_LoadHTML(webView, html, baseUrl);
#elif UNITY_ANDROID
- if (webView == null)
- return 0;
- return webView.Get("progress");
+ if (webView == null)
+ return;
+ webView.Call("LoadHTML", html, baseUrl);
#endif
- }
-
- public bool CanGoBack()
- {
+ }
+
+ public void EvaluateJS(string js)
+ {
+#if UNITY_WEBGL
+#if !UNITY_EDITOR
+ _gree_unity_webview_evaluateJS(name, js);
+#endif
+#elif UNITY_WEBPLAYER
+ Application.ExternalCall("unityWebView.evaluateJS", name, js);
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_EvaluateJS(webView, js);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("EvaluateJS", js);
+#endif
+ }
+
+ public int Progress()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
- return false;
+ //TODO: UNSUPPORTED
+ return 0;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return 0;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- return false;
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
+ if (webView == IntPtr.Zero)
+ return 0;
+ return _CWebViewPlugin_Progress(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return 0;
+ return webView.Get("progress");
+#endif
+ }
+
+ public bool CanGoBack()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
return false;
- return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoBack(webView);
#elif UNITY_ANDROID
- if (webView == null)
+ if (webView == null)
+ return false;
+ return webView.Get("canGoBack");
+#endif
+ }
+
+ public bool CanGoForward()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
return false;
- return webView.Get("canGoBack");
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return false;
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return false;
+ return _CWebViewPlugin_CanGoForward(webView);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return false;
+ return webView.Get("canGoForward");
#endif
- }
-
- public bool CanGoForward()
- {
+ }
+
+ public void GoBack()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
- return false;
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- return false;
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return false;
- return _CWebViewPlugin_CanGoForward(webView);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoBack(webView);
#elif UNITY_ANDROID
- if (webView == null)
- return false;
- return webView.Get("canGoForward");
+ if (webView == null)
+ return;
+ webView.Call("GoBack");
#endif
- }
-
- public void GoBack()
- {
+ }
+
+ public void GoForward()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_GoBack(webView);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GoForward(webView);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("GoBack");
+ if (webView == null)
+ return;
+ webView.Call("GoForward");
#endif
- }
-
- public void GoForward()
- {
+ }
+
+ public void Reload()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_GoForward(webView);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_Reload(webView);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("GoForward");
+ if (webView == null)
+ return;
+ webView.Call("Reload");
#endif
- }
-
- public void CallOnError(string error)
- {
- if (onError != null)
+ }
+
+ public void CallOnError(string error)
{
- onError(error);
+ if (onError != null)
+ {
+ onError(error);
+ }
}
- }
-
- public void CallOnHttpError(string error)
- {
- if (onHttpError != null)
+
+ public void CallOnHttpError(string error)
{
- onHttpError(error);
+ if (onHttpError != null)
+ {
+ onHttpError(error);
+ }
}
- }
-
- public void CallOnStarted(string url)
- {
- if (onStarted != null)
+
+ public void CallOnStarted(string url)
{
- onStarted(url);
+ if (onStarted != null)
+ {
+ onStarted(url);
+ }
}
- }
-
- public void CallOnLoaded(string url)
- {
- if (onLoaded != null)
+
+ public void CallOnLoaded(string url)
{
- onLoaded(url);
+ if (onLoaded != null)
+ {
+ onLoaded(url);
+ }
}
- }
-
- public void CallFromJS(string message)
- {
- if (onJS != null)
+
+ public void CallFromJS(string message)
+ {
+ if (onJS != null)
+ {
+#if !UNITY_ANDROID
+#if UNITY_2018_4_OR_NEWER
+ message = UnityWebRequest.UnEscapeURL(message);
+#else // UNITY_2018_4_OR_NEWER
+ message = WWW.UnEscapeURL(message);
+#endif // UNITY_2018_4_OR_NEWER
+#endif // !UNITY_ANDROID
+ onJS(message);
+ }
+ }
+
+ public void CallOnHooked(string message)
{
+ if (onHooked != null)
+ {
#if !UNITY_ANDROID
#if UNITY_2018_4_OR_NEWER
- message = UnityWebRequest.UnEscapeURL(message);
+ message = UnityWebRequest.UnEscapeURL(message);
#else // UNITY_2018_4_OR_NEWER
- message = WWW.UnEscapeURL(message);
+ message = WWW.UnEscapeURL(message);
#endif // UNITY_2018_4_OR_NEWER
#endif // !UNITY_ANDROID
- onJS(message);
+ onHooked(message);
+ }
}
- }
-
-
- public void AddCustomHeader(string headerKey, string headerValue)
- {
+
+ public void CallOnCookies(string cookies)
+ {
+ if (onCookies != null)
+ {
+ onCookies(cookies);
+ }
+ }
+
+ public void AddCustomHeader(string headerKey, string headerValue)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_AddCustomHeader(webView, headerKey, headerValue);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("AddCustomHeader", headerKey, headerValue);
+ if (webView == null)
+ return;
+ webView.Call("AddCustomHeader", headerKey, headerValue);
#endif
- }
-
- public string GetCustomHeaderValue(string headerKey)
- {
+ }
+
+ public string GetCustomHeaderValue(string headerKey)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
- return null;
+ //TODO: UNSUPPORTED
+ return null;
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+ return null;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- return null;
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return null;
- return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
+ if (webView == IntPtr.Zero)
+ return null;
+ return _CWebViewPlugin_GetCustomHeaderValue(webView, headerKey);
#elif UNITY_ANDROID
- if (webView == null)
- return null;
- return webView.Call("GetCustomHeaderValue", headerKey);
+ if (webView == null)
+ return null;
+ return webView.Call("GetCustomHeaderValue", headerKey);
#endif
- }
-
- public void RemoveCustomHeader(string headerKey)
- {
+ }
+
+ public void RemoveCustomHeader(string headerKey)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_RemoveCustomHeader(webView, headerKey);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("RemoveCustomHeader", headerKey);
+ if (webView == null)
+ return;
+ webView.Call("RemoveCustomHeader", headerKey);
#endif
- }
-
- public void ClearCustomHeader()
- {
+ }
+
+ public void ClearCustomHeader()
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_ClearCustomHeader(webView);
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCustomHeader(webView);
#elif UNITY_ANDROID
- if (webView == null)
- return;
- webView.Call("ClearCustomHeader");
+ if (webView == null)
+ return;
+ webView.Call("ClearCustomHeader");
#endif
- }
-
- public void ClearCookies()
- {
+ }
+
+ public void ClearCookie(string url, string name)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookie(url, name);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookie", url, name);
+#endif
+ }
+
+ public void ClearCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("ClearCookies");
+#endif
+ }
+
+
+ public void SaveCookies()
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SaveCookies();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SaveCookies");
+#endif
+ }
+
+
+ public void GetCookies(string url)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_GetCookies(webView, url);
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("GetCookies", url);
+#else
+ //TODO: UNSUPPORTED
+#endif
+ }
+
+ public void SetBasicAuthInfo(string userName, string password)
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 basic auth not implemented in native plugin
+#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
+ //TODO: UNSUPPORTED
+#elif UNITY_IPHONE
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_SetBasicAuthInfo(webView, userName, password);
+#elif UNITY_ANDROID
+ if (webView == null)
+ return;
+ webView.Call("SetBasicAuthInfo", userName, password);
+#endif
+ }
+
+ public void ClearCache(bool includeDiskFiles)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
+ //TODO: WebView2 cache clear not implemented in native plugin
#elif UNITY_IPHONE && !UNITY_EDITOR
- if (webView == IntPtr.Zero)
- return;
- _CWebViewPlugin_ClearCookies();
+ if (webView == IntPtr.Zero)
+ return;
+ _CWebViewPlugin_ClearCache(webView, includeDiskFiles);
#elif UNITY_ANDROID && !UNITY_EDITOR
- if (webView == null)
- return;
- webView.Call("ClearCookies");
+ if (webView == null)
+ return;
+ webView.Call("ClearCache", includeDiskFiles);
#endif
- }
-
-
- public string GetCookies(string url)
- {
+ }
+
+
+ public void SetTextZoom(int textZoom)
+ {
#if UNITY_WEBPLAYER || UNITY_WEBGL
- //TODO: UNSUPPORTED
- return "";
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
- //TODO: UNSUPPORTED
- return "";
+ //TODO: WebView2 text zoom not implemented in native plugin
#elif UNITY_IPHONE && !UNITY_EDITOR
- if (webView == IntPtr.Zero)
- return "";
- return _CWebViewPlugin_GetCookies(url);
+ //TODO: UNSUPPORTED
#elif UNITY_ANDROID && !UNITY_EDITOR
- if (webView == null)
- return "";
- return webView.Call("GetCookies", url);
-#else
- //TODO: UNSUPPORTED
- return "";
+ if (webView == null)
+ return;
+ webView.Call("SetTextZoom", textZoom);
#endif
- }
-
-
+ }
+
+ public void SetMixedContentMode(int mode) // 0: MIXED_CONTENT_ALWAYS_ALLOW, 1: MIXED_CONTENT_NEVER_ALLOW, 2: MIXED_CONTENT_COMPATIBILITY_MODE
+ {
+#if UNITY_WEBPLAYER || UNITY_WEBGL
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_LINUX || UNITY_SERVER
+ //TODO: UNSUPPORTED
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ //TODO: WebView2 mixed content mode not implemented in native plugin
+#elif UNITY_IPHONE && !UNITY_EDITOR
+ //TODO: UNSUPPORTED
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (webView == null)
+ return;
+ webView.Call("SetMixedContentMode", mode);
+#endif
+ }
+
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
- void OnApplicationFocus(bool focus)
- {
- hasFocus = focus;
- }
-
- void Update()
- {
- if (hasFocus) {
- inputString += Input.inputString;
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
}
- for (;;) {
- if (webView == IntPtr.Zero)
- break;
- string s = _CWebViewPlugin_GetMessage(webView);
- if (s == null)
- break;
- switch (s[0]) {
- case 'E':
- CallOnError(s.Substring(1));
- break;
- case 'S':
- CallOnStarted(s.Substring(1));
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null) {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus) {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;) {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i)) {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap) {
+ var w = _CWebViewPlugin_BitmapWidth(webView);
+ var h = _CWebViewPlugin_BitmapHeight(webView);
+ var f = (_CWebViewPlugin_BitmapARGB(webView)) ? TextureFormat.ARGB32 : TextureFormat.RGBA32;
+ if (w > 0 && h > 0) {
+ if (texture == null || texture.width != w || texture.height != h) {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, f, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+#if UNITY_2018_2_OR_NEWER
+#else
+ textureDataBuffer = new byte[w * h * 4];
+#endif
+ }
+ if (texture != null) {
+#if UNITY_2018_2_OR_NEWER
+ var ptr = _CWebViewPlugin_Render(webView, IntPtr.Zero);
+ if (ptr != IntPtr.Zero) {
+ texture.LoadRawTextureData(ptr, w * h * 4);
+ texture.Apply();
+ }
+#else
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+#endif
+ }
+ }
+ }
+ }
+
+ void UpdateBGTransform()
+ {
+ if (bg != null) {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ public int bitmapRefreshCycle = 1;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type) {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
break;
- case 'L':
- CallOnLoaded(s.Substring(1));
+ }
+ switch (Event.current.type) {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
break;
- case 'J':
- CallFromJS(s.Substring(1));
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString)) {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0) {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null) {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix
+ = Matrix4x4.TRS(
+ new Vector3(0, Screen.height, 0),
+ Quaternion.identity,
+ new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
break;
}
}
- }
-
- public int bitmapRefreshCycle = 1;
-
- void OnGUI()
- {
- if (webView == IntPtr.Zero || !visibility)
- return;
-
- Vector3 pos = Input.mousePosition;
- bool down = Input.GetButton("Fire1");
- bool press = Input.GetButtonDown("Fire1");
- bool release = Input.GetButtonUp("Fire1");
- float deltaY = Input.GetAxis("Mouse ScrollWheel");
- bool keyPress = false;
- string keyChars = "";
- short keyCode = 0;
- if (inputString != null && inputString.Length > 0) {
- keyPress = true;
- keyChars = inputString.Substring(0, 1);
- keyCode = (short)inputString[0];
- inputString = inputString.Substring(1);
- }
- bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
- _CWebViewPlugin_Update(webView,
- (int)(pos.x - rect.x), (int)(pos.y - rect.y), deltaY,
- down, press, release, keyPress, keyCode, keyChars,
- refreshBitmap);
- if (refreshBitmap) {
+#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+ void OnApplicationFocus(bool focus)
+ {
+ if (!focus)
+ {
+ hasFocus = false;
+ }
+ }
+
+ void Start()
+ {
+ if (canvas != null)
+ {
+ var g = new GameObject(gameObject.name + "BG");
+ g.transform.parent = canvas.transform;
+ bg = g.AddComponent();
+ UpdateBGTransform();
+ }
+ }
+
+ void Update()
+ {
+ if (bg != null)
+ {
+ bg.transform.SetAsLastSibling();
+ }
+#if !ENABLE_INPUT_SYSTEM
+ if (hasFocus)
+ {
+ inputString += Input.inputString;
+ }
+#endif
+ for (;;)
+ {
+ if (webView == IntPtr.Zero)
+ break;
+ string s = _CWebViewPlugin_GetMessage(webView);
+ if (s == null)
+ break;
+ var i = s.IndexOf(':', 0);
+ if (i == -1)
+ continue;
+ switch (s.Substring(0, i))
+ {
+ case "CallFromJS":
+ CallFromJS(s.Substring(i + 1));
+ break;
+ case "CallOnError":
+ CallOnError(s.Substring(i + 1));
+ break;
+ case "CallOnHttpError":
+ CallOnHttpError(s.Substring(i + 1));
+ break;
+ case "CallOnLoaded":
+ CallOnLoaded(s.Substring(i + 1));
+ break;
+ case "CallOnStarted":
+ CallOnStarted(s.Substring(i + 1));
+ break;
+ case "CallOnHooked":
+ CallOnHooked(s.Substring(i + 1));
+ break;
+ case "CallOnCookies":
+ CallOnCookies(s.Substring(i + 1));
+ break;
+ }
+ }
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ bool refreshBitmap = (Time.frameCount % bitmapRefreshCycle == 0);
+ _CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio);
+ if (refreshBitmap)
{
var w = _CWebViewPlugin_BitmapWidth(webView);
var h = _CWebViewPlugin_BitmapHeight(webView);
- if (texture == null || texture.width != w || texture.height != h) {
- texture = new Texture2D(w, h, TextureFormat.RGBA32, false, true);
- texture.filterMode = FilterMode.Bilinear;
- texture.wrapMode = TextureWrapMode.Clamp;
+ if (w > 0 && h > 0)
+ {
+ if (texture == null || texture.width != w || texture.height != h)
+ {
+ bool isLinearSpace = QualitySettings.activeColorSpace == ColorSpace.Linear;
+ texture = new Texture2D(w, h, TextureFormat.RGBA32, false, !isLinearSpace);
+ texture.filterMode = FilterMode.Bilinear;
+ texture.wrapMode = TextureWrapMode.Clamp;
+ textureDataBuffer = new byte[w * h * 4];
+ }
+ if (textureDataBuffer != null && textureDataBuffer.Length > 0)
+ {
+ var gch = GCHandle.Alloc(textureDataBuffer, GCHandleType.Pinned);
+ _CWebViewPlugin_Render(webView, gch.AddrOfPinnedObject());
+ gch.Free();
+ texture.LoadRawTextureData(textureDataBuffer);
+ texture.Apply();
+ }
}
}
- _CWebViewPlugin_SetTextureId(webView, (int)texture.GetNativeTexturePtr());
- _CWebViewPlugin_SetCurrentInstance(webView);
-#if UNITY_4_6 || UNITY_5_0 || UNITY_5_1
- GL.IssuePluginEvent(-1);
-#else
- GL.IssuePluginEvent(GetRenderEventFunc(), -1);
-#endif
}
- if (texture != null) {
- Matrix4x4 m = GUI.matrix;
- GUI.matrix
- = Matrix4x4.TRS(
- new Vector3(0, Screen.height, 0),
- Quaternion.identity,
- new Vector3(1, -1, 1));
- GUI.DrawTexture(rect, texture);
- GUI.matrix = m;
+
+ void UpdateBGTransform()
+ {
+ if (bg != null)
+ {
+ bg.rectTransform.anchorMin = Vector2.zero;
+ bg.rectTransform.anchorMax = Vector2.zero;
+ bg.rectTransform.pivot = Vector2.zero;
+ bg.rectTransform.position = rect.min;
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x);
+ bg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y);
+ }
+ }
+
+ // On Windows, CapturePreview is heavy; default 10 = refresh every 10th frame for better FPS.
+ public int bitmapRefreshCycle = 10;
+ public int devicePixelRatio = 1;
+
+ void OnGUI()
+ {
+ if (webView == IntPtr.Zero || !visibility)
+ return;
+ switch (Event.current.type)
+ {
+ case EventType.MouseDown:
+ case EventType.MouseUp:
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ hasFocus = rect.Contains(p);
+ }
+ break;
+ }
+ switch (Event.current.type)
+ {
+ case EventType.MouseMove:
+ case EventType.MouseDown:
+ case EventType.MouseDrag:
+ case EventType.MouseUp:
+ case EventType.ScrollWheel:
+ if (hasFocus)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ int mouseState = 0;
+ float delta = 0;
+ if (Event.current.type == EventType.MouseDown)
+ mouseState = 1;
+ else if (Event.current.type == EventType.MouseDrag)
+ mouseState = 2;
+ else if (Event.current.type == EventType.MouseUp)
+ mouseState = 3;
+ else if (Event.current.type == EventType.ScrollWheel)
+ delta = -Event.current.delta.y;
+ _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, delta, mouseState);
+ }
+ break;
+ case EventType.Repaint:
+ while (!string.IsNullOrEmpty(inputString))
+ {
+ var keyChars = inputString.Substring(0, 1);
+ var keyCode = (ushort)inputString[0];
+ inputString = inputString.Substring(1);
+ if (!string.IsNullOrEmpty(keyChars) || keyCode != 0)
+ {
+ var p = Event.current.mousePosition;
+ p.y = Screen.height - p.y;
+ p.x -= rect.x;
+ p.y -= rect.y;
+ _CWebViewPlugin_SendKeyEvent(webView, (int)p.x, (int)p.y, keyChars, keyCode, 1);
+ }
+ }
+ if (texture != null)
+ {
+ Matrix4x4 m = GUI.matrix;
+ GUI.matrix = Matrix4x4.TRS(new Vector3(0, Screen.height, 0), Quaternion.identity, new Vector3(1, -1, 1));
+ Graphics.DrawTexture(rect, texture);
+ GUI.matrix = m;
+ }
+ break;
+ }
}
- }
#endif
+ }
}
diff --git a/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_cht.md b/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_cht.md
new file mode 100644
index 00000000..351954c5
--- /dev/null
+++ b/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_cht.md
@@ -0,0 +1,46 @@
+---
+name: 修復 Windows 點擊視窗縮小
+overview: 點擊 APP 畫面時視窗被縮小,極可能是因為外掛在處理滑鼠/鍵盤時對「離屏的 WebView2 宿主視窗」呼叫 SetFocus,導致前景(Unity)視窗失去啟用狀態;部分環境下會觸發「失去焦點時最小化」。計劃在原生層移除或修正 SetFocus 使用,並可選加入視窗樣式避免宿主被啟用。
+todos: []
+isProject: false
+---
+
+# 修復 Windows 平台點擊後 APP 視窗被縮小的問題
+
+## 問題說明
+
+- **現象**:建置出的 Windows APP 啟動後,點擊畫面任何地方,整個 APP 視窗會「突然被縮小」(多數情況為被最小化到工作列)。
+- **發生時機**:與點擊一致,故高度懷疑與「滑鼠事件轉發到 WebView2」時的視窗焦點/啟用狀態有關。
+
+## 根因分析
+
+- 外掛使用**離屏的 Win32 視窗**承載 WebView2(`SetWindowPos(..., -32000, -32000)` + `SW_SHOWNOACTIVATE`),滑鼠與鍵盤經由 `PostMessage` 送到該視窗的 STA 線程處理。
+- 在 [WebViewPlugin.cpp](WebViewPlugin.cpp) 中:
+ - **滑鼠**:`WM_WEBVIEW_SEND_MOUSE` 處理時,若走 **非 CompositionController 的 fallback 路徑**(約 484–491 行),會對子視窗/宿主呼叫 `**SetFocus(target)`**(約 487 行)。
+ - **鍵盤**:`WM_WEBVIEW_SEND_KEY` 處理時(約 503–530 行)**一律**對 target 呼叫 `**SetFocus(target)`**(約 511 行)。
+- Windows 文件指出:**SetFocus 會啟用接收焦點的視窗或其父視窗**。對離屏的宿主視窗呼叫 `SetFocus` 會把「啟用視窗」從 Unity 轉到外掛的視窗,Unity 視窗失去前景,若專案或系統有「失去焦點時最小化」之類行為,就會出現點擊後視窗被縮小的現象。
+- 即使主要輸入路徑使用 CompositionController 的 `SendMouseInput`(該路徑未呼叫 SetFocus),若 fallback 被用到,或鍵盤路徑在點擊後被間接觸發,仍會發生 SetFocus 導致的前景切換。
+
+## 修復方向
+
+1. **滑鼠路徑**:在 `WM_WEBVIEW_SEND_MOUSE` 的 **else 分支**(非 CompositionController)中**移除 `SetFocus(target)`**,避免僅因滑鼠點擊就把焦點/啟用給離屏視窗。
+2. **鍵盤路徑**:在 `WM_WEBVIEW_SEND_KEY` 中**避免讓 Unity 視窗失去前景**:
+ - **方案 A(建議先做)**:在呼叫 `SetFocus(target)` 前以 `GetForegroundWindow()` 記下目前前景視窗,送完按鍵後用 `SetForegroundWindow(保存的 HWND)` 把前景還給 Unity。注意:`SetForegroundWindow` 有跨行程/執行緒限制,若還原失敗可再考慮 AttachThreadInput 或改為不呼叫 SetFocus。
+ - **方案 B**:若方案 A 在實機上仍無法穩定還原前景,則改為**不呼叫 SetFocus**,僅以 `SendMessage(WM_CHAR/KEYDOWN/KEYUP)` 送鍵;部分情境下 WebView2 仍可能處理(可實測鍵盤輸入是否仍正常)。
+3. **可選強化**:建立離屏宿主視窗時加上 `**WS_EX_NOACTIVATE`**,使該視窗在收到點擊或焦點時**不要被啟用**,降低任何殘留的 SetFocus 或內部邏輯把前景帶走的機會(需驗證是否影響 WebView2 輸入與 IME)。
+
+## 實作要點(僅列出需改檔案與位置)
+
+- **檔案**:[WebViewPlugin.cpp](WebViewPlugin.cpp)
+ - **WM_WEBVIEW_SEND_MOUSE**(約 460–501 行):在 `else` 分支中刪除 `if (data->mouseState == 1) SetFocus(target);`(約 487 行)。
+ - **WM_WEBVIEW_SEND_KEY**(約 503–530 行):在現有 `SetFocus(target)` 前後加入「取得目前前景視窗 → 送鍵 → 設回前景」;若使用方案 B 則改為不呼叫 SetFocus,僅保留 SendMessage 送鍵。
+ - **CreateWindowExW**(約 554 行):若採用可選強化,將 `WS_EX_TOOLWINDOW` 改為 `WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE`(並在文件中註明需測試輸入與 IME)。
+
+## 驗證建議
+
+- 建置 Windows 64 位元 Standalone,在「點擊 WebView 區域」與「點擊非 WebView 區域」各測數次,確認視窗不再被縮小或最小化。
+- 確認 WebView 內連結、輸入框、滑鼠選取與鍵盤輸入仍正常;若啟用 WS_EX_NOACTIVATE,需額外測 IME 與焦點相關行為。
+
+## 若「縮小」是視窗被「縮小尺寸」而非最小化
+
+- 若實際是視窗 **resize** 變小而非最小化,則需再查:Unity Player 設定、是否有其他地方送 `WM_SIZE`/`SW_MINIMIZE`、或全螢幕/解析度相關邏輯。目前程式碼中未發現對宿主或 Unity 送 resize/最小化訊息,先以「焦點/啟用導致最小化」為主要假設實施上述修改。
diff --git a/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_en.md b/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_en.md
new file mode 100644
index 00000000..1900136d
--- /dev/null
+++ b/plugins/Windows/PlanDocument/unity_webview_windows_click_window_shrink_fix_plan_en.md
@@ -0,0 +1,46 @@
+---
+name: Fix Windows Click Window Shrink
+overview: When the app window shrinks (often minimizes) after clicking, the likely cause is the plugin calling SetFocus on the off-screen WebView2 host window while handling mouse/keyboard, which deactivates the foreground (Unity) window; some environments then trigger "minimize on focus loss". This plan removes or corrects SetFocus usage in native code and optionally adds window styles to prevent the host from being activated.
+todos: []
+isProject: false
+---
+
+# Fix: Windows App Window Shrinks After Click
+
+## Problem Description
+
+- **Symptom**: After building and running the Windows app, clicking anywhere on the screen causes the entire app window to "suddenly shrink" (in most cases, it is minimized to the taskbar).
+- **When it happens**: It coincides with the click, so it is strongly suspected to be related to window focus/activation when mouse events are forwarded to WebView2.
+
+## Root Cause Analysis
+
+- The plugin uses an **off-screen Win32 window** to host WebView2 (`SetWindowPos(..., -32000, -32000)` + `SW_SHOWNOACTIVATE`). Mouse and keyboard events are sent to that window's STA thread via `PostMessage`.
+- In [WebViewPlugin.cpp](WebViewPlugin.cpp):
+ - **Mouse**: In `WM_WEBVIEW_SEND_MOUSE` handling, when the **fallback path (non-CompositionController)** is used (around lines 484–491), it calls `**SetFocus(target)**` on the child/host window (around line 487).
+ - **Keyboard**: In `WM_WEBVIEW_SEND_KEY` handling (around lines 503–530), it **always** calls `**SetFocus(target)**` on the target (around line 511).
+- Windows documentation states that **SetFocus activates the window (or its parent) that receives focus**. Calling `SetFocus` on the off-screen host window moves the "active window" from Unity to the plugin's window; the Unity window loses foreground. If the project or system has behavior such as "minimize on focus loss", the window will shrink after a click.
+- Even when the main input path uses CompositionController's `SendMouseInput` (which does not call SetFocus), if the fallback is used or the keyboard path is triggered indirectly after a click, SetFocus can still cause the foreground to switch.
+
+## Fix Direction
+
+1. **Mouse path**: In the **else branch** of `WM_WEBVIEW_SEND_MOUSE` (non-CompositionController), **remove `SetFocus(target)`** so that a mouse click alone does not give focus/activation to the off-screen window.
+2. **Keyboard path**: In `WM_WEBVIEW_SEND_KEY`, **avoid making the Unity window lose foreground**:
+ - **Option A (recommended first)**: Before calling `SetFocus(target)`, save the current foreground window with `GetForegroundWindow()`. After sending the key, use `SetForegroundWindow(saved_hwnd)` to restore the foreground to Unity. Note: `SetForegroundWindow` has cross-process/thread restrictions; if restoration fails, consider AttachThreadInput or not calling SetFocus.
+ - **Option B**: If Option A still cannot reliably restore the foreground on real devices, **do not call SetFocus** and only send keys via `SendMessage(WM_CHAR/KEYDOWN/KEYUP)`; WebView2 may still handle input in some cases (verify that keyboard input still works).
+3. **Optional hardening**: When creating the off-screen host window, add **`WS_EX_NOACTIVATE`** so that the window **is not activated** when it receives a click or focus, reducing the chance that any remaining SetFocus or internal logic steals the foreground (verify that this does not affect WebView2 input and IME).
+
+## Implementation Notes (files and locations only)
+
+- **File**: [WebViewPlugin.cpp](WebViewPlugin.cpp)
+ - **WM_WEBVIEW_SEND_MOUSE** (around lines 460–501): In the `else` branch, remove `if (data->mouseState == 1) SetFocus(target);` (around line 487).
+ - **WM_WEBVIEW_SEND_KEY** (around lines 503–530): Before/after the existing `SetFocus(target)`, add "get current foreground window → send key → restore foreground"; if using Option B, do not call SetFocus and keep only SendMessage for key delivery.
+ - **CreateWindowExW** (around line 554): If using the optional hardening, change `WS_EX_TOOLWINDOW` to `WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE` (and document that input and IME need to be tested).
+
+## Verification Suggestions
+
+- Build Windows 64-bit Standalone; test multiple times by clicking on the WebView area and on the non-WebView area, and confirm the window no longer shrinks or minimizes.
+- Confirm that links, input fields, mouse selection, and keyboard input still work inside the WebView; if `WS_EX_NOACTIVATE` is enabled, additionally test IME and focus-related behavior.
+
+## If "Shrink" Means Window Resize Rather Than Minimize
+
+- If the window actually **resizes** (gets smaller) rather than minimizing, then also check: Unity Player settings, any other code sending `WM_SIZE`/`SW_MINIMIZE`, or fullscreen/resolution logic. The current code does not show any resize/minimize messages sent to the host or Unity; the above changes are based on the main assumption that focus/activation causes minimization.
diff --git a/plugins/Windows/PlanDocument/unity_webview_windows_plan_cht.md b/plugins/Windows/PlanDocument/unity_webview_windows_plan_cht.md
new file mode 100644
index 00000000..67d7c1ec
--- /dev/null
+++ b/plugins/Windows/PlanDocument/unity_webview_windows_plan_cht.md
@@ -0,0 +1,216 @@
+---
+name: Unity WebView Windows 支援
+overview: 在現有 unity-webview 插件中新增 Windows(含 Editor)支援:以 Microsoft WebView2 為後端,實作與 Mac 版對齊的 C API 原生 DLL,並在 WebViewObject.cs 中接上對應分支,使 Windows 上也能疊加顯示 WebView 並與 JS 互通。
+todos: []
+isProject: false
+---
+
+# Unity WebView 插件 — Windows 平台支援計劃
+
+## 專案現況摘要
+
+- **已支援**: Android(Java/AAR)、iOS(WKWebView .mm)、Mac Editor/Standalone(WebView.bundle + WKWebView)、WebGL(jslib)。
+- **未支援**: Windows / Linux / Server — 在 [plugins/WebViewObject.cs](plugins/WebViewObject.cs) 中,所有 `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` 分支僅輸出錯誤或空實作(約 30+ 處)。
+- **Mac 實作模式**: 原生 bundle 匯出 C 函式(`_CWebViewPlugin_Init`, `LoadURL`, `GetMessage`, `Update`, `Render` 等),C# 以 `[DllImport("WebView")]` 呼叫;離屏 WKWebView 以 `takeSnapshotWithConfiguration` 取得點陣圖,寫入 Unity 提供的 buffer,由 Unity 在 `OnGUI` 繪製並處理滑鼠/鍵盤。
+
+---
+
+## 技術方案:採用 WebView2 + 與 Mac 對齊的 C API
+
+- **後端**: Microsoft **WebView2**(Edge Chromium),Windows 10+ 建議方案,且具 [CapturePreview](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2#capturepreview) 可取得 bitmap,適合與現有「離屏渲染 → 貼到 Unity」流程一致。
+- **介面**: 新增 **Windows 專用 C++ Win32 DLL**,匯出與 Mac 相同的 C 函式名稱與簽名(或盡量一致),以便在 [WebViewObject.cs](plugins/WebViewObject.cs) 用同一套 `#elif UNITY_STANDALONE_WIN` / `UNITY_EDITOR_WIN` 邏輯,僅 DllImport 的 library 名稱改為 Windows 的 DLL(例如 `"WebView"` 或 `"WebViewPlugin"`)。
+- **參考**: 開源專案 [UnityWebView2](https://github.com/umetaman/UnityWebView2) 可作為 WebView2 與 Unity 整合的參考;本專案則需額外對齊現有 C API 與訊息格式(如 `CallFromJS`, `CallOnLoaded` 等),以保持與 Android/iOS/Mac 行為一致。
+
+---
+
+## 架構與資料流(概念)
+
+```mermaid
+flowchart LR
+ subgraph Unity
+ WebViewObject[WebViewObject.cs]
+ Update[Update: GetMessage / Update / Render]
+ OnGUI[OnGUI: Draw texture, Input]
+ end
+ subgraph Native
+ DLL[WebView.dll]
+ WV2[WebView2]
+ end
+ WebViewObject -->|DllImport| DLL
+ Update -->|GetMessage, Update, Render| DLL
+ DLL --> WV2
+ WV2 -->|CapturePreview| DLL
+ DLL -->|bitmap buffer| Update
+```
+
+- Unity 每幀:`GetMessage` 取回原生層排隊的訊息(JS 呼叫、載入完成等);`Update(..., refreshBitmap, dpr)` 驅動擷圖;`Render` 將像素寫入 C# 提供的 `byte[]`;`OnGUI` 用該 texture 繪製並轉發滑鼠/鍵盤事件到原生。
+
+---
+
+## 實作項目
+
+### 1. Windows 原生插件(C++ Win32 DLL)
+
+- **位置**: 新增目錄,例如 `plugins/Windows/`(或 `plugins/Win/`),內含 Visual Studio 專案與原始碼。
+- **依賴**: [WebView2 SDK](https://learn.microsoft.com/en-us/microsoft-edge/webview2/)(C++,NuGet 或手動取用 `WebView2.h` / `WebView2Loader.dll` 等)。
+- **需實作的 C API**(與 [plugins/Mac/Sources/WebView.mm](plugins/Mac/Sources/WebView.mm) 的 `extern "C"` 對齊):
+ - 必要:`_CWebViewPlugin_Init`, `_CWebViewPlugin_Destroy`, `_CWebViewPlugin_SetRect`, `_CWebViewPlugin_SetVisibility`, `_CWebViewPlugin_LoadURL`, `_CWebViewPlugin_LoadHTML`, `_CWebViewPlugin_EvaluateJS`, `_CWebViewPlugin_Progress`, `_CWebViewPlugin_CanGoBack`, `_CWebViewPlugin_CanGoForward`, `_CWebViewPlugin_GoBack`, `_CWebViewPlugin_GoForward`, `_CWebViewPlugin_Reload`, `_CWebViewPlugin_Update`, `_CWebViewPlugin_BitmapWidth`, `_CWebViewPlugin_BitmapHeight`, `_CWebViewPlugin_Render`, `_CWebViewPlugin_GetMessage`, `_CWebViewPlugin_SendMouseEvent`, `_CWebViewPlugin_SendKeyEvent`.
+ - 可選(與 Mac 一致):`_CWebViewPlugin_GetAppPath`, `_CWebViewPlugin_InitStatic`, `_CWebViewPlugin_IsInitialized`, `_CWebViewPlugin_SetURLPattern`, `_CWebViewPlugin_AddCustomHeader`, `_CWebViewPlugin_GetCustomHeaderValue`, `_CWebViewPlugin_RemoveCustomHeader`, `_CWebViewPlugin_ClearCustomHeader`, `_CWebViewPlugin_ClearCookie`, `_CWebViewPlugin_ClearCookies`, `_CWebViewPlugin_SaveCookies`, `_CWebViewPlugin_GetCookies`。
+- **實作要點**:
+ - 使用**隱藏視窗**承載 `CoreWebView2`,以便在無可見視窗時仍能載入與執行 JS。
+ - 在適當時機(例如 `Update(refreshBitmap=true)`)呼叫 WebView2 的 **CapturePreview**,將結果寫入 `Render(instance, textureBuffer)` 的 buffer(RGBA 等格式需與 Mac/Unity 一致)。
+ - 訊息佇列:在 WebView2 的導覽/載入完成、JS 透過 `postMessage` 等回傳時,將字串推入佇列,格式與現有一致(例如 `"CallFromJS:..."`, `"CallOnLoaded:..."`),由 C# 的 `GetMessage` 輪詢取回。
+ - **執行緒**: WebView2 需在 STA 且需 message pump;若 Unity 主線程非 STA,需在 DLL 內建立專用 STA 線程與隱藏視窗,並用 PostMessage/同步機制與呼叫端溝通,避免死鎖。
+- **輸出**: 建置產出 `WebView.dll`(或 `WebViewPlugin.dll`),放置於 Unity 的 `Assets/Plugins/x86_64/`(64 位元);若有 32 位元需求則另建 `x86`。
+
+### 2. C# 端修改(WebViewObject.cs)
+
+- **檔案**: [plugins/WebViewObject.cs](plugins/WebViewObject.cs)(修改後需同步至 dist/package 與 dist/package-nofragment,或由既有 build 流程拷貝)。
+- **作法**:
+ - 將所有目前標記為 `//TODO: UNSUPPORTED` 的 `#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN`(以及必要時的 `UNITY_EDITOR_LINUX || UNITY_SERVER` 分離,僅 Windows 啟用)改為**實際實作**。
+ - 新增 `#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` 區塊:
+ - 宣告與 Mac 相同的 DllImport(library 名改為 `"WebView"` 或實際 DLL 名稱),以及與 Mac 相同的成員(如 `IntPtr webView`, `Rect rect`, `Texture2D texture`, `byte[] textureDataBuffer` 等)。
+ - `Init`: 呼叫 `_CWebViewPlugin_InitStatic`(Windows 可為空實作)、`_CWebViewPlugin_Init`,並設定 `rect`。
+ - `SetMargins` / `SetCenterPositionWithScale`: 換算後呼叫 `_CWebViewPlugin_SetRect`。
+ - `SetVisibility`, `LoadURL`, `LoadHTML`, `EvaluateJS`, `GoBack`, `GoForward`, `Reload`, `SetURLPattern`, Cookie/Header 等:轉發至對應 C API。
+ - 在 `Update` 中:輪詢 `_CWebViewPlugin_GetMessage` 並依前綴分派到 `CallFromJS` / `CallOnLoaded` 等;呼叫 `_CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio)`;若 `refreshBitmap` 則取 `BitmapWidth`/`BitmapHeight`、`Render` 寫入 `textureDataBuffer` 並 `texture.LoadRawTextureData`/`Apply`。
+ - 在 `OnGUI` 中:處理滑鼠/鍵盤事件並呼叫 `_CWebViewPlugin_SendMouseEvent` / `_CWebViewPlugin_SendKeyEvent`,以及用 `Graphics.DrawTexture` 繪製 texture(可沿用 Mac 的座標轉換)。
+ - Mac 專用的 `_CWebViewPlugin_GetAppPath` 在 Windows 可回傳固定字串或空,不影響核心流程;`InitStatic` 在 Windows 可為 no-op。
+- **條件編譯**: 保持 `UNITY_EDITOR_LINUX || UNITY_SERVER` 維持不支援,僅加入 `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` 的實作,避免影響現有平台。
+
+### 3. 插件放置與 meta
+
+- **目錄**: 在 dist 的 package 中新增(或由 build 腳本複製):
+ - `Assets/Plugins/x86_64/WebView.dll`(64 位元 Windows)
+ - 可選:`Assets/Plugins/x86/WebView.dll`(32 位元)
+- **.meta**: 為上述 DLL 建立 PluginImporter,僅勾選 **Editor (Windows)** 與 **Standalone (Windows / Win64)**,對應 CPU 為 x86 或 x86_64,與現有 [WebView.bundle.meta](dist/package/Assets/Plugins/WebView.bundle.meta) 的 Mac 設定方式類似。
+
+### 4. WebView2 Runtime 依賴
+
+- 執行時需已安裝 **WebView2 Runtime**(多數 Win10/11 已內建,或由使用者安裝)。
+- 可選:在說明文件(README 或本插件文件)中註明需求,或提供 [固定版本 Runtime 隨應用分發](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution) 的建議。
+
+### 5. Build 與打包流程
+
+- 若專案有 Rake / Packager([build/](build/)):在打包 dist 時加入「建置 Windows DLL」與「複製 WebView.dll 到 dist/package/Assets/Plugins/x86_64」的步驟。
+- 需在 CI 或本機具備 Visual Studio(含 C++ 桌面開發)以建置該 DLL。
+
+### 6. 文件與 README
+
+- 在 [README.md](README.md) 的「Platform-Specific Notes」中新增 **Windows** 一節:說明支援 Editor 與 Standalone、需 WebView2 Runtime、以及若有 32/64 或特殊權限需求時的注意事項。
+- 將首段「Windows is not supported」改為支援 Windows(Editor + Standalone)。
+
+---
+
+## 風險與注意事項
+
+- **執行緒與 COM**: WebView2 的 COM 與 UI 線程要求嚴格,需在 DLL 內妥善處理 STA 與 message pump,否則易崩潰或卡住。
+- **CapturePreview 效能**: 每幀擷圖可能較耗資源,可與 Mac 一樣用 `bitmapRefreshCycle` 降頻(例如每 N 幀擷一次)。
+- **首次體驗**: 若使用者未安裝 WebView2 Runtime,需有明確錯誤訊息或導向下載頁;可考慮在 C# 端偵測 DLL 載入失敗時提示。
+
+---
+
+## 建議實作順序
+
+1. 建立 `plugins/Windows/` 專案,實作最小可行 C API(Init, Destroy, SetRect, LoadURL, GetMessage, Update, Render, 滑鼠/鍵盤),並用隱藏視窗 + CapturePreview 產出 bitmap。
+2. 在 WebViewObject.cs 加入 `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` 的 DllImport 與 Init/Update/OnGUI 邏輯,先不處理 Cookie/Header 等進階 API。
+3. 在 Unity Editor(Windows)與 Standalone 建置中測試基本載入、JS 互通與輸入。
+4. 補齊其餘 C API(Cookie、CustomHeader、URL pattern 等)與 C# 對應分支。
+5. 整合至 build 流程、撰寫 README,並視需要加入 WebView2 Runtime 偵測或說明。
+
+---
+
+## 實作後修正與補充(已納入)
+
+以下為實際實作與除錯過程中完成的修正與新增處理,已反映在程式與文件中。
+
+### 滑鼠與鍵盤輸入
+
+- **問題**: 以一般 `CreateCoreWebView2Controller` 建立視窗並用 `SendMessage(WM_LBUTTONDOWN` 等)對 `Chrome_WidgetWin_0` 送滑鼠時,Chromium 會忽略合成訊息,導致點連結、選文字、輸入框焦點無效。
+- **作法**: 改為使用 **CreateCoreWebView2CompositionController**(需 `ICoreWebView2Environment3`),取得 `ICoreWebView2CompositionController` 後,滑鼠改由 **SendMouseInput** 轉發(`COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE` / `LEFT_BUTTON_DOWN` / `LEFT_BUTTON_UP` / `WHEEL`),座標為 WebView 客戶區且 Y 軸與 Unity 一致需翻轉。鍵盤仍對宿主 hwnd(或子視窗)送 `WM_CHAR` / `WM_KEYDOWN` / `WM_KEYUP`。
+- **結果**: 左鍵點連結可正常導覽、可選取文字;鍵盤可在輸入框輸入。
+
+### 效能與擷圖頻率
+
+- **CapturePreview** 較耗資源,預設改為每 N 幀擷一次(C# 端 `bitmapRefreshCycle`,Windows 預設可設為 3~10 以兼顧 FPS 與流暢度),並在 README 說明可調。
+
+### 資料目錄與權限
+
+- WebView2 的 `userDataFolder` 不可放在無寫入權限的路徑(例如 Program Files 下)。改為使用 `%LOCALAPPDATA%\UnityWebView2`,失敗時再退回執行檔所在目錄。
+
+### 宿主視窗與輸入
+
+- 宿主視窗改為「可見但移出螢幕」(例如 `SetWindowPos(..., -32000, -32000)` + `ShowWindow(SW_SHOWNOACTIVATE)`),避免完全隱藏時部分輸入行為異常;仍由 CompositionController 的 SendMouseInput 負責滑鼠。
+
+### Lambda capture 編譯錯誤
+
+- 在 `CreateCoreWebView2EnvironmentWithOptions` 的完成 callback 中,對 `env->QueryInterface(IID_PPV_ARGS(&env3))` 的結果若使用外層變數 `hr`,lambda 未 capture 會導致 C3493/C2326。改為在 lambda 內宣告區域變數(例如 `HRESULT hrQI`)存放 QueryInterface 結果即可。
+
+### Debug 日誌
+
+- 外掛內建 `OutputDebugString` 日誌(`WV_LOG`),由 `WebViewPlugin.cpp` 頂端 **WEBVIEW_DEBUG** 控制(預設 **0** = 關閉)。設為 1 重新建置後可用 DebugView 或 Visual Studio 輸出檢視滑鼠/鍵盤與視窗資訊,方便排查輸入問題。說明見 `plugins/Windows/README.md` 的「Debug 日誌」一節。
+
+### 32 位元 (x86) 平台建置支援
+
+- **問題**: 最初手動建立的 C++ 專案檔 (`.vcxproj` 與 `.sln`) 中,誤將 32 位元平台命名為 `x86`,但 Visual Studio / MSBuild 在 C++ 專案中強制要求 32 位元平台代號必須為 **`Win32`**,導致建置 32 位元版本時報錯 (`未包含 Release|Win32 的組態和平台組合`)。
+- **作法**: 將 `WebViewPlugin.vcxproj` 與 `WebViewPlugin.sln` 中的 `x86` 平台字眼全數修正為 `Win32`。
+- **結果**: 在 Visual Studio 中可順利選擇 `Win32` 平台進行建置,產出的 32 位元 `WebView.dll` 放置於 Unity 專案的 `Assets/Plugins/x86/` 目錄下即可支援 32 位元 Windows 應用程式。
+
+### 隱藏工作列圖示
+
+- **問題**: 執行 Unity 專案時,Windows 工作列會多出一個沒有名稱的空白應用程式圖示。這是因為承載 WebView2 的隱藏視窗預設使用了 `WS_OVERLAPPEDWINDOW` 樣式。
+- **作法**: 在 `WebViewPlugin.cpp` 的 `CreateWindowExW` 呼叫中,將視窗樣式改為 `WS_POPUP`,並加上擴充樣式 `WS_EX_TOOLWINDOW`。
+- **結果**: 該隱藏視窗不再顯示於 Windows 工作列上,且不影響 WebView2 的離屏渲染與輸入轉發。
+
+### GetMessage 回傳緩衝區:CoTaskMemAlloc
+
+- **問題**: `_CWebViewPlugin_GetMessage` 回傳的字串緩衝區原先以 `malloc()` 配置。在 P/Invoke 下,.NET/Mono 執行期預期以 `Marshal.FreeCoTaskMem`(即 `CoTaskMemFree()`)釋放這類緩衝區;混用不同配置器可能造成洩漏或未定義行為。
+- **作法**: 改以 `CoTaskMemAlloc()` 配置訊息緩衝區(需 include ``、連結 `ole32.lib`)。marshaller 在將回傳的 `const char*` 轉成 C# 字串時會以 `CoTaskMemFree()` 釋放。
+- **參考**: [Mono P/Invoke](https://www.mono-project.com/docs/advanced/pinvoke/) — 跨越 managed/unmanaged 邊界的記憶體應使用執行期配置器(Windows 上為 COM task memory allocator)。
+
+### Destroy 競態:等 STA 清理完再釋放 instance
+
+- **問題**: 在 `_CWebViewPlugin_Destroy` 中,程式對 STA 執行緒送出 `WM_WEBVIEW_DESTROY` 後,立即從 `s_instances` 移除並釋放 `WebViewInstance`。此時 STA 執行緒仍可能正在存取 `inst`(例如在 WndProc 處理 `WM_WEBVIEW_DESTROY` 時),造成 use-after-free 與關閉應用時偶發崩潰。
+- **作法**: 建立「清理完成」事件,並在 PostMessage 送出 `WM_WEBVIEW_DESTROY` 時以 `lParam` 傳入。WndProc 中在釋放 COM 參考、關閉 handle 後呼叫 `SetEvent(destroyDoneEvent)`,再呼叫 `DestroyWindow(hwnd)`。在 `_CWebViewPlugin_Destroy` 中先以 `WaitForSingleObject(destroyDoneEvent, 10000)` 等待,再從 `s_instances` 移除 instance 並關閉事件 handle。
+- **結果**: 僅在 STA 執行緒完成所有清理後才釋放 instance,消除 use-after-free 與關閉時的崩潰。
+
+### 非阻塞擷圖與雙緩衝(避免主線程阻塞)
+
+- **問題**: `_CWebViewPlugin_Update` 原先在 `refreshBitmap` 時建立 Event、對 STA 送出 `WM_WEBVIEW_CAPTURE` 後以 `WaitForSingleObject(ev, 5000)` 等待,導致主線程(Unity)每幀可能阻塞最多 5 秒。
+- **作法**:
+ - **非阻塞**: 僅在「目前沒有擷取在進行」時才發送一次擷取:主線程以 `captureInProgress`(`std::atomic`)為旗標,若 `!captureInProgress.exchange(true)` 才 `PostMessage(inst->hwnd, WM_WEBVIEW_CAPTURE, 0, 0)`;不再建立/傳遞 Event,也不呼叫 `WaitForSingleObject`。`captureInProgress` 在 STA 的 CapturePreview 完成 callback 中設回 `false`;若 `WM_WEBVIEW_CAPTURE` 開頭發現 `!inst || !inst->webview` 也設回 `false`。
+ - **雙緩衝**: 在 `WebViewInstance` 中新增後端緩衝 `bitmapPixelsBack`、`bitmapWidthBack`、`bitmapHeightBack`。STA 的 CapturePreview 完成後,先解碼到區域緩衝再寫入 `bitmapPixelsBack`,然後在 `bitmapMutex` 下與 `bitmapPixels` 做 swap(及同步 width/height),使 `_CWebViewPlugin_Render` 始終只讀取前端的 `bitmapPixels`,無需等待擷取完成且不會讀到半寫入狀態。
+- **結果**: 主線程不再因擷圖而阻塞;Render 僅讀取當前已交換好的前端 buffer,與 macOS 的雙緩衝做法一致。
+
+---
+
+## 阻塞點檢查(其他可能造成「開啟網頁時卡住」的地方)
+
+以下為針對「載入完成了 webView 卻卡住」可能原因的檢查結果。
+
+### 1. Init 阻塞(最可能原因)
+
+- **位置**: `_CWebViewPlugin_Init` 內 `WaitForSingleObject(params.readyEvent, 30000)`(約第 655 行)。
+- **行為**: 建立 WebView 時,主線程會**同步等待** STA 線程完成 `CreateCoreWebView2EnvironmentWithOptions` 與 `CreateCoreWebView2CompositionController` 的**非同步**完成 callback,最多等 **30 秒**。若系統較慢、首次啟動 WebView2、或防毒/網路延遲,初始化可能需數秒,期間 **Unity 主線程完全卡住**,畫面會像「開啟網頁時卡住」。
+- **建議**:
+ - 已將逾時由 30 秒改為 **10 秒**,失敗時較快回傳,避免長時間凍結。
+ - 若需完全避免卡住,可改為「非同步 Init」:C API 立即回傳一個 pending 的 instance,由 C# 在 Update 中輪詢 `IsInitialized` 或透過 callback 得知就緒後再呼叫 LoadURL 等(需較大改動)。
+
+### 2. Destroy 阻塞
+
+- **位置**: `_CWebViewPlugin_Destroy` 內 `WaitForSingleObject(destroyDoneEvent, 10000)`(約第 680 行)。
+- **行為**: 關閉時主線程最多等 10 秒讓 STA 清理完。通常只在關閉時發生,不影響「開啟網頁」時的卡住感。
+
+### 3. PostToInstanceAndWait
+
+- **狀態**: 目前程式內**未使用**,僅定義。不會造成阻塞。
+
+### 4. Mutex 競爭(BitmapWidth / BitmapHeight / Render)
+
+- **行為**: 主線程在每幀呼叫 `BitmapWidth`、`BitmapHeight`、`Render` 時會取得 `bitmapMutex`。STA 端在雙緩衝實作下僅在 swap 時短暫持鎖,解碼在鎖外完成,持鎖時間極短,一般不會造成明顯卡頓。
+
+### 小結
+
+- **「開啟網頁時卡住」** 最可能來自 **Init 的 30 秒同步等待**;已將逾時改為 10 秒以減輕影響。
+- 擷圖造成的阻塞已由「非阻塞擷圖 + 雙緩衝」排除。
+- 其餘阻塞點(Destroy、未使用的 PostToInstanceAndWait、短暫 mutex)在正常情境下不至於造成載入完成後仍卡住的現象。
diff --git a/plugins/Windows/PlanDocument/unity_webview_windows_plan_en.md b/plugins/Windows/PlanDocument/unity_webview_windows_plan_en.md
new file mode 100644
index 00000000..22d651b0
--- /dev/null
+++ b/plugins/Windows/PlanDocument/unity_webview_windows_plan_en.md
@@ -0,0 +1,214 @@
+---
+name: Unity WebView Windows Support
+overview: Add Windows (including Editor) support to the existing unity-webview plugin: use Microsoft WebView2 as the backend, implement a native DLL with a C API aligned to the Mac version, and wire the corresponding branches in WebViewObject.cs so that WebView can be overlaid and JS interop works on Windows.
+todos: []
+isProject: false
+---
+
+# Unity WebView Plugin — Windows Platform Support Plan
+
+## Current Project Summary
+
+- **Supported**: Android (Java/AAR), iOS (WKWebView .mm), Mac Editor/Standalone (WebView.bundle + WKWebView), WebGL (jslib).
+- **Not supported**: Windows / Linux / Server — In [plugins/WebViewObject.cs](plugins/WebViewObject.cs), all `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` branches only output errors or empty stubs (30+ places).
+- **Mac implementation pattern**: Native bundle exports C functions (`_CWebViewPlugin_Init`, `LoadURL`, `GetMessage`, `Update`, `Render`, etc.); C# calls them via `[DllImport("WebView")]`. Offscreen WKWebView uses `takeSnapshotWithConfiguration` to obtain a bitmap, written into a Unity-provided buffer and drawn in `OnGUI` with mouse/keyboard handling.
+
+---
+
+## Technical Approach: WebView2 + C API Aligned with Mac
+
+- **Backend**: Microsoft **WebView2** (Edge Chromium), the recommended option for Windows 10+, with [CapturePreview](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2#capturepreview) for bitmap capture, matching the existing “offscreen render → composite in Unity” flow.
+- **Interface**: Add a **Windows-only C++ Win32 DLL** exporting the same C function names and signatures as Mac (or as close as possible), so [WebViewObject.cs](plugins/WebViewObject.cs) can use the same `#elif UNITY_STANDALONE_WIN` / `UNITY_EDITOR_WIN` logic with only the DllImport library name changed to the Windows DLL (e.g. `"WebView"` or `"WebViewPlugin"`).
+- **Reference**: The open-source [UnityWebView2](https://github.com/umetaman/UnityWebView2) project can be used as a reference for WebView2 + Unity integration; this project must additionally align the C API and message formats (e.g. `CallFromJS`, `CallOnLoaded`) with existing behavior on Android/iOS/Mac.
+
+---
+
+## Architecture and Data Flow (Concept)
+
+```mermaid
+flowchart LR
+ subgraph Unity
+ WebViewObject[WebViewObject.cs]
+ Update[Update: GetMessage / Update / Render]
+ OnGUI[OnGUI: Draw texture, Input]
+ end
+ subgraph Native
+ DLL[WebView.dll]
+ WV2[WebView2]
+ end
+ WebViewObject -->|DllImport| DLL
+ Update -->|GetMessage, Update, Render| DLL
+ DLL --> WV2
+ WV2 -->|CapturePreview| DLL
+ DLL -->|bitmap buffer| Update
+```
+
+- Each frame Unity: `GetMessage` retrieves queued messages from native (JS calls, load complete, etc.); `Update(..., refreshBitmap, dpr)` drives capture; `Render` writes pixels into the C#-provided `byte[]`; `OnGUI` draws the texture and forwards mouse/keyboard events to native.
+
+---
+
+## Implementation Items
+
+### 1. Windows Native Plugin (C++ Win32 DLL)
+
+- **Location**: New directory, e.g. `plugins/Windows/` (or `plugins/Win/`), containing the Visual Studio project and source.
+- **Dependencies**: [WebView2 SDK](https://learn.microsoft.com/en-us/microsoft-edge/webview2/) (C++, via NuGet or manual use of `WebView2.h` / `WebView2Loader.dll`, etc.).
+- **C API to implement** (aligned with [plugins/Mac/Sources/WebView.mm](plugins/Mac/Sources/WebView.mm) `extern "C"`):
+ - Required: `_CWebViewPlugin_Init`, `_CWebViewPlugin_Destroy`, `_CWebViewPlugin_SetRect`, `_CWebViewPlugin_SetVisibility`, `_CWebViewPlugin_LoadURL`, `_CWebViewPlugin_LoadHTML`, `_CWebViewPlugin_EvaluateJS`, `_CWebViewPlugin_Progress`, `_CWebViewPlugin_CanGoBack`, `_CWebViewPlugin_CanGoForward`, `_CWebViewPlugin_GoBack`, `_CWebViewPlugin_GoForward`, `_CWebViewPlugin_Reload`, `_CWebViewPlugin_Update`, `_CWebViewPlugin_BitmapWidth`, `_CWebViewPlugin_BitmapHeight`, `_CWebViewPlugin_Render`, `_CWebViewPlugin_GetMessage`, `_CWebViewPlugin_SendMouseEvent`, `_CWebViewPlugin_SendKeyEvent`.
+ - Optional (Mac parity): `_CWebViewPlugin_GetAppPath`, `_CWebViewPlugin_InitStatic`, `_CWebViewPlugin_IsInitialized`, `_CWebViewPlugin_SetURLPattern`, `_CWebViewPlugin_AddCustomHeader`, `_CWebViewPlugin_GetCustomHeaderValue`, `_CWebViewPlugin_RemoveCustomHeader`, `_CWebViewPlugin_ClearCustomHeader`, `_CWebViewPlugin_ClearCookie`, `_CWebViewPlugin_ClearCookies`, `_CWebViewPlugin_SaveCookies`, `_CWebViewPlugin_GetCookies`.
+- **Implementation notes**:
+ - Use a **hidden window** to host `CoreWebView2` so loading and JS execution work without a visible window.
+ - When appropriate (e.g. `Update(refreshBitmap=true)`), call WebView2 **CapturePreview** and write the result into the buffer used by `Render(instance, textureBuffer)` (RGBA format must match Mac/Unity).
+ - Message queue: On WebView2 navigation/load complete and JS `postMessage` etc., push strings in the existing format (e.g. `"CallFromJS:..."`, `"CallOnLoaded:..."`) for C# `GetMessage` to poll.
+ - **Threading**: WebView2 requires STA and a message pump; if the Unity main thread is not STA, create a dedicated STA thread and hidden window inside the DLL and use PostMessage/synchronization to communicate with the caller and avoid deadlocks.
+- **Output**: Build produces `WebView.dll` (or `WebViewPlugin.dll`) for Unity’s `Assets/Plugins/x86_64/` (64-bit); add `x86` if 32-bit is needed.
+
+### 2. C# Changes (WebViewObject.cs)
+
+- **File**: [plugins/WebViewObject.cs](plugins/WebViewObject.cs) (after edits, sync to dist/package and dist/package-nofragment, or rely on existing build copy).
+- **Approach**:
+ - Replace all current `//TODO: UNSUPPORTED` `#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` (and separate `UNITY_EDITOR_LINUX || UNITY_SERVER` if needed so only Windows is enabled) with **real implementations**.
+ - Add `#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` blocks:
+ - Declare the same DllImports as Mac (library name changed to `"WebView"` or the actual DLL name) and the same members (e.g. `IntPtr webView`, `Rect rect`, `Texture2D texture`, `byte[] textureDataBuffer`).
+ - `Init`: Call `_CWebViewPlugin_InitStatic` (Windows can be a no-op), `_CWebViewPlugin_Init`, and set `rect`.
+ - `SetMargins` / `SetCenterPositionWithScale`: After conversion, call `_CWebViewPlugin_SetRect`.
+ - `SetVisibility`, `LoadURL`, `LoadHTML`, `EvaluateJS`, `GoBack`, `GoForward`, `Reload`, `SetURLPattern`, Cookie/Header, etc.: Forward to the corresponding C API.
+ - In `Update`: Poll `_CWebViewPlugin_GetMessage` and dispatch by prefix to `CallFromJS` / `CallOnLoaded` etc.; call `_CWebViewPlugin_Update(webView, refreshBitmap, devicePixelRatio)`; when `refreshBitmap` get `BitmapWidth`/`BitmapHeight`, `Render` into `textureDataBuffer`, then `texture.LoadRawTextureData`/`Apply`.
+ - In `OnGUI`: Handle mouse/keyboard and call `_CWebViewPlugin_SendMouseEvent` / `_CWebViewPlugin_SendKeyEvent`, and draw the texture with `Graphics.DrawTexture` (reuse Mac’s coordinate transform if applicable).
+ - Mac-only `_CWebViewPlugin_GetAppPath` can return a fixed string or empty on Windows; `InitStatic` can be a no-op on Windows.
+- **Conditional compilation**: Keep `UNITY_EDITOR_LINUX || UNITY_SERVER` unsupported; only add implementations for `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` to avoid affecting other platforms.
+
+### 3. Plugin Placement and meta
+
+- **Directories**: In dist package (or via build script): `Assets/Plugins/x86_64/WebView.dll` (64-bit Windows); optionally `Assets/Plugins/x86/WebView.dll` (32-bit).
+- **.meta**: Create PluginImporter for the DLL, enabling only **Editor (Windows)** and **Standalone (Windows / Win64)** for the appropriate CPU (x86 or x86_64), similar to [WebView.bundle.meta](dist/package/Assets/Plugins/WebView.bundle.meta) for Mac.
+
+### 4. WebView2 Runtime Dependency
+
+- **WebView2 Runtime** must be installed at run time (many Win10/11 systems have it; otherwise the user installs it).
+- Optionally document the requirement in README or plugin docs, or provide guidance on [distributing a fixed Runtime with the app](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution).
+
+### 5. Build and Packaging
+
+- If the project uses Rake/Packager ([build/](build/)): add steps to build the Windows DLL and copy `WebView.dll` to `dist/package/Assets/Plugins/x86_64`.
+- CI or local machine must have Visual Studio with C++ desktop development to build the DLL.
+
+### 6. Documentation and README
+
+- Add a **Windows** section under “Platform-Specific Notes” in [README.md](README.md): Editor and Standalone support, WebView2 Runtime requirement, and any 32/64 or permission notes.
+- Change the opening “Windows is not supported” to state that Windows (Editor + Standalone) is supported.
+
+---
+
+## Risks and Considerations
+
+- **Threading and COM**: WebView2’s COM and UI thread requirements are strict; the DLL must handle STA and message pump correctly or crashes/hangs are likely.
+- **CapturePreview performance**: Per-frame capture can be costly; use `bitmapRefreshCycle` (e.g. capture every N frames) as on Mac.
+- **First-run experience**: If WebView2 Runtime is missing, provide a clear error or link to the download page; consider detecting DLL load failure in C# and prompting.
+
+---
+
+## Suggested Implementation Order
+
+1. Create the `plugins/Windows/` project and implement the minimal C API (Init, Destroy, SetRect, LoadURL, GetMessage, Update, Render, mouse/keyboard) with a hidden window and CapturePreview for the bitmap.
+2. Add `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` DllImport and Init/Update/OnGUI logic in WebViewObject.cs; skip Cookie/Header and other advanced APIs initially.
+3. Test basic load, JS interop, and input in Unity Editor (Windows) and Standalone builds.
+4. Complete the remaining C API (Cookie, CustomHeader, URL pattern, etc.) and corresponding C# branches.
+5. Integrate into the build process, update README, and add WebView2 Runtime detection or instructions if needed.
+
+---
+
+## Post-Implementation Fixes and Additions (Incorporated)
+
+The following fixes and additions were made during implementation and debugging and are reflected in the code and docs.
+
+### Mouse and Keyboard Input
+
+- **Issue**: With the standard `CreateCoreWebView2Controller` and `SendMessage(WM_LBUTTONDOWN`, etc.) to `Chrome_WidgetWin_0`, Chromium ignores synthetic messages, so link clicks, text selection, and input focus did not work.
+- **Approach**: Switch to **CreateCoreWebView2CompositionController** (requires `ICoreWebView2Environment3`). After obtaining `ICoreWebView2CompositionController`, forward mouse via **SendMouseInput** (`COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE` / `LEFT_BUTTON_DOWN` / `LEFT_BUTTON_UP` / `WHEEL`) with client-area coordinates (Y flipped to match Unity). Keyboard continues to be sent to the host hwnd (or child) via `WM_CHAR` / `WM_KEYDOWN` / `WM_KEYUP`.
+- **Result**: Left-click navigation, text selection, and keyboard input in form fields work correctly.
+
+### Performance and Capture Rate
+
+- **CapturePreview** is costly; default to capturing every N frames (C# `bitmapRefreshCycle`; on Windows a default of 3–10 balances FPS and smoothness). Document the setting in README.
+
+### Data Directory and Permissions
+
+- WebView2 `userDataFolder` must not be on a read-only path (e.g. under Program Files). Use `%LOCALAPPDATA%\UnityWebView2`, falling back to the executable directory if needed.
+
+### Host Window and Input
+
+- Make the host window “visible but off-screen” (e.g. `SetWindowPos(..., -32000, -32000)` + `ShowWindow(SW_SHOWNOACTIVATE)`) to avoid input quirks when fully hidden; mouse is still handled via CompositionController’s SendMouseInput.
+
+### Lambda Capture Build Error
+
+- In the completion callback of `CreateCoreWebView2EnvironmentWithOptions`, using the outer variable `hr` for the result of `env->QueryInterface(IID_PPV_ARGS(&env3))` causes C3493/C2326 because the lambda does not capture it. Use a local variable (e.g. `HRESULT hrQI`) inside the lambda for the QueryInterface result.
+
+### Debug Logging
+
+- The plugin has `OutputDebugString` logging (`WV_LOG`) controlled by **WEBVIEW_DEBUG** at the top of `WebViewPlugin.cpp` (default **0** = off). Set to 1 and rebuild to inspect mouse/keyboard and window info via DebugView or Visual Studio output when debugging input. See the “Debug logging” section in `plugins/Windows/README.md`.
+
+### 32-bit (x86) Platform Build Support
+
+- **Issue**: In the manually created C++ project files (`.vcxproj` and `.sln`), the 32-bit platform was mistakenly named `x86`. However, Visual Studio and MSBuild strictly require the 32-bit platform identifier in C++ projects to be **`Win32`**, causing build errors for the 32-bit version (e.g., `Project does not contain a configuration and platform combination of Release|Win32`).
+- **Approach**: Replaced all instances of `x86` with `Win32` in `WebViewPlugin.vcxproj` and `WebViewPlugin.sln`.
+- **Result**: You can now successfully select the `Win32` platform in Visual Studio to build. The resulting 32-bit `WebView.dll` should be placed in the `Assets/Plugins/x86/` directory of your Unity project to support 32-bit Windows applications.
+
+### Hide Taskbar Icon
+
+- **Issue**: When running the Unity project, a blank, unnamed application icon would appear on the Windows taskbar. This was because the hidden window hosting WebView2 was created with the default `WS_OVERLAPPEDWINDOW` style.
+- **Approach**: In the `CreateWindowExW` call within `WebViewPlugin.cpp`, changed the window style to `WS_POPUP` and added the extended style `WS_EX_TOOLWINDOW`.
+- **Result**: The hidden window no longer shows up on the Windows taskbar, and it does not affect WebView2's offscreen rendering or input forwarding.
+
+### GetMessage return buffer: CoTaskMemAlloc
+
+- **Issue**: `_CWebViewPlugin_GetMessage` returned a string buffer allocated with `malloc()`. For P/Invoke, the .NET/Mono runtime expects to free such buffers with `Marshal.FreeCoTaskMem` (i.e. `CoTaskMemFree()`); mixing allocators can cause leaks or undefined behavior.
+- **Approach**: Allocate the message buffer with `CoTaskMemAlloc()` (include ``, link `ole32.lib`). The marshaller then frees it with `CoTaskMemFree()` when marshalling the returned `const char*` to a C# string.
+- **Reference**: [Mono P/Invoke](https://www.mono-project.com/docs/advanced/pinvoke/) — memory passed across the managed/unmanaged boundary should use the runtime’s allocator (on Windows, the COM task memory allocator).
+
+### Destroy race: wait for STA cleanup before freeing instance
+
+- **Issue**: In `_CWebViewPlugin_Destroy`, the code posted `WM_WEBVIEW_DESTROY` to the STA thread and then immediately erased the `WebViewInstance` from `s_instances`, freeing the object. The STA thread could still be accessing `inst` (e.g. in the WndProc when handling `WM_WEBVIEW_DESTROY`), leading to use-after-free and intermittent crashes on app exit.
+- **Approach**: Create a “destroy done” event; pass it as `lParam` when posting `WM_WEBVIEW_DESTROY`. In the WndProc, after releasing COM references and closing handles, call `SetEvent(destroyDoneEvent)` and then `DestroyWindow(hwnd)`. In `_CWebViewPlugin_Destroy`, call `WaitForSingleObject(destroyDoneEvent, 10000)` before erasing the instance from `s_instances` and closing the event handle.
+- **Result**: The instance is only destroyed after the STA thread has finished all cleanup, eliminating the use-after-free and the associated exit crash.
+
+### Non-blocking capture and double-buffering (avoid blocking main thread)
+
+- **Issue**: `_CWebViewPlugin_Update` previously created an event, posted `WM_WEBVIEW_CAPTURE` to the STA thread, and then called `WaitForSingleObject(ev, 5000)`, blocking the main (Unity) thread for up to 5 seconds per frame when `refreshBitmap` was true.
+- **Approach**:
+ - **Non-blocking**: Start a new capture only when none is in progress. The main thread uses a `captureInProgress` flag (`std::atomic`): only when `!captureInProgress.exchange(true)` does it call `PostMessage(inst->hwnd, WM_WEBVIEW_CAPTURE, 0, 0)`; no event is created or passed, and `WaitForSingleObject` is removed. `captureInProgress` is set back to `false` in the STA thread's CapturePreview completion callback, and also in the `WM_WEBVIEW_CAPTURE` handler when `!inst || !inst->webview`.
+ - **Double-buffering**: Add a back buffer in `WebViewInstance` (`bitmapPixelsBack`, `bitmapWidthBack`, `bitmapHeightBack`). After CapturePreview completes on the STA thread, decode into a local buffer, write into `bitmapPixelsBack`, then under `bitmapMutex` swap with `bitmapPixels` and update width/height. `_CWebViewPlugin_Render` always reads only the front buffer `bitmapPixels`, so it never waits on capture and never sees a half-updated buffer.
+- **Result**: The main thread no longer blocks on capture; Render reads only the current front buffer, consistent with the macOS double-buffering approach.
+
+---
+
+## Blocking-point review (other possible causes of “stuck when opening page”)
+
+Findings for cases where the page has loaded but the app still feels stuck.
+
+### 1. Init blocking (most likely cause)
+
+- **Location**: Inside `_CWebViewPlugin_Init`, `WaitForSingleObject(params.readyEvent, 30000)` (around line 655).
+- **Behavior**: When creating the WebView, the main thread **synchronously waits** for the STA thread to complete the **asynchronous** callbacks of `CreateCoreWebView2EnvironmentWithOptions` and `CreateCoreWebView2CompositionController`, for up to **30 seconds**. On a slow system, first-run WebView2, or under antivirus/network delay, initialization can take several seconds, during which the **Unity main thread is fully blocked** and the app appears stuck when “opening the page.”
+- **Recommendation**:
+ - The timeout has been reduced from 30 seconds to **10 seconds** so that on failure or slow init the main thread does not stay blocked as long.
+ - To avoid blocking entirely, Init could be made asynchronous: the C API returns immediately with a “pending” instance, and C# polls `IsInitialized` in Update or uses a callback before calling LoadURL, etc. (larger change).
+
+### 2. Destroy blocking
+
+- **Location**: Inside `_CWebViewPlugin_Destroy`, `WaitForSingleObject(destroyDoneEvent, 10000)` (around line 680).
+- **Behavior**: On close, the main thread waits up to 10 seconds for STA cleanup. This only affects shutdown, not “opening” or “loading.”
+
+### 3. PostToInstanceAndWait
+
+- **Status**: **Not used** anywhere in the code; only defined. Does not cause blocking.
+
+### 4. Mutex contention (BitmapWidth / BitmapHeight / Render)
+
+- **Behavior**: The main thread takes `bitmapMutex` when calling `BitmapWidth`, `BitmapHeight`, and `Render` each frame. With double-buffering, the STA thread holds the lock only briefly during the swap; decoding is done outside the lock, so contention is minimal and should not cause noticeable stutter.
+
+### Summary
+
+- **“Stuck when opening the page”** is most likely due to **Init’s synchronous wait** (formerly up to 30 seconds). The timeout has been reduced to 10 seconds to limit the impact.
+- Capture-related blocking has been removed by non-blocking capture and double-buffering.
+- Other blocking (Destroy, unused PostToInstanceAndWait, brief mutex) should not explain the app feeling stuck after load.
diff --git a/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_cht.md b/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_cht.md
new file mode 100644
index 00000000..72f41ebf
--- /dev/null
+++ b/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_cht.md
@@ -0,0 +1,78 @@
+---
+name: WebView 卡住與 Destroy 當機修復
+overview: 根據 Crash 報告,當在 Windows 編輯器載入特定網頁(如 interserv.com.tw)後按停止時,Unity 會在 Destroy 流程中於 WebView2(EmbeddedBrowserWebView.dll)內崩潰。原因與「載入中/忙碌時銷毀」以及主線與 STA 線程的競態有關,可透過 Destroy 前先停止導覽、C# 端避免 Destroy 期間再呼叫 GetMessage、以及可選的逾時後安全處理來緩解。
+todos: []
+isProject: false
+---
+
+# WebView 卡住與按下停止後 Unity 當機修復計畫
+
+## 問題摘要
+
+- **現象**:Windows 編輯器中載入 `https://www.interserv.com.tw/#service` 時 WebView 易卡住,按下停止後 Unity 當機。
+- **Crash 位置**:`EmbeddedBrowserWebView.dll` 的 `GetHandleVerifier`(exception_code 0x80000003),呼叫鏈包含 `CWebViewPlugin_Destroy` 與 `CWebViewPlugin_GetMessage`。
+- **推測原因**:
+ 1. 該頁面較重(腳本/資源多、hash 導覽),WebView2 在「載入中」或忙碌時被銷毀,COM 釋放時內部狀態不穩定導致崩潰。
+ 2. Destroy 時主線程可能仍在同一幀呼叫 `GetMessage`/Update,與 Destroy 競態。
+ 3. 若 STA 線程被 WebView2 卡住(例如導覽或腳本未結束),`WM_WEBVIEW_DESTROY` 無法及時處理,逾時後主線程仍從 `s_instances` 移除 instance,可能造成後續 use-after-free 或 COM 釋放順序問題。
+
+## 修復方向
+
+### 1. 在 WM_WEBVIEW_DESTROY 中先停止導覽再釋放 COM(核心)
+
+**檔案**:[plugins/Windows/WebViewPlugin.cpp](c:_Work\Unity_Practice\unity-webview_Syaoran\plugins\Windows\WebViewPlugin.cpp)
+
+在將 `controller` / `compositionController` / `webview` 設為 `nullptr` 之前:
+
+- 若 `inst->webview` 有效,先呼叫 `inst->webview->Stop()`,讓 WebView2 取消進行中的導覽並盡量離開忙碌狀態。
+- 可選:再 `Navigate(L"about:blank")` 清空內容(若 Stop 後同步等待可能增加複雜度,可先只做 Stop)。
+
+這樣可降低「在載入中銷毀」導致 WebView2 內部檢查(如 GetHandleVerifier)失敗的機率。
+
+**程式位置**:約 353–368 行,`case WM_WEBVIEW_DESTROY` 內,在 `inst->controller = nullptr` 之前加入對 `inst->webview->Stop()` 的呼叫,並注意 `inst` 可能為 null 的檢查。
+
+### 2. C# OnDestroy:先清空 webView 再呼叫 Destroy(避免競態)
+
+**檔案**:例如 [plugins/WebViewObject.cs](c:_Work\Unity_Practice\unity-webview_Syaoran\plugins\WebViewObject.cs)(或 dist/package 內對應檔)
+
+在 `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` 分支中:
+
+- 若 `webView != IntPtr.Zero`,先將指標存到區域變數,然後**立即**將 `webView` 設為 `IntPtr.Zero`,再呼叫 `_CWebViewPlugin_Destroy(保存的指標)`。
+- 這樣同一幀內若 Update 仍執行,會因 `webView == IntPtr.Zero` 而不再呼叫 `GetMessage`/Update/Render,避免在 Destroy 過程中再進入原生層或 WebView2 相關路徑,減少與 EmbeddedBrowserWebView 的競態。
+
+**程式位置**:約 836–845 行,改為:
+
+```csharp
+if (webView == IntPtr.Zero) return;
+var ptr = webView;
+webView = IntPtr.Zero;
+_CWebViewPlugin_Destroy(ptr);
+```
+
+(其餘 Destroy texture、bg 等維持不變。)
+
+### 3. 逾時後不從 s_instances 移除(可選,降低 use-after-free 風險)
+
+**檔案**:[plugins/Windows/WebViewPlugin.cpp](c:_Work\Unity_Practice\unity-webview_Syaoran\plugins\Windows\WebViewPlugin.cpp)
+
+目前 `_CWebViewPlugin_Destroy` 在 `WaitForSingleObject(destroyDoneEvent, 10000)` 逾時後仍會從 `s_instances` 移除並釋放 instance。若 STA 線程之後才處理 `WM_WEBVIEW_DESTROY`,可能觸及已刪除的 instance 或 COM 釋放順序異常。
+
+- **選項 A**:逾時時不從 `s_instances` 移除該 instance(僅記錄或略過),避免主線程釋放後 STA 仍使用該指標;缺點是 instance 會殘留直到程序結束。
+- **選項 B**:維持現狀但加上註解說明風險,並依賴 1、2 降低逾時發生機率。
+
+建議先實作 1 與 2,若仍發生當機再考慮選項 A。
+
+### 4. 網址本身與「卡住」的關係
+
+- `#service` 為前端 hash 導覽,可能觸發大量 JS 或動態載入,使 WebView2 長時間忙碌。
+- 「卡住」可能來自:長時間腳本、多個導覽/iframe、或 CapturePreview 在複雜頁面上較慢。這些都會讓 STA 線程較晚回到訊息迴圈,若此時使用者按停止,Destroy 更容易在「忙碌狀態」下執行,因此**先 Stop 再釋放**特別重要。
+
+---
+
+## 實作順序建議
+
+1. **必做**:在 `WM_WEBVIEW_DESTROY` 中對 `inst->webview` 呼叫 `Stop()` 再釋放 COM。
+2. **必做**:C# OnDestroy(Windows)改為先 `webView = IntPtr.Zero` 再 `_CWebViewPlugin_Destroy(ptr)`。
+3. **可選**:若仍有崩潰,再考慮 Destroy 逾時時不從 `s_instances` 移除,或加入「destroying」旗標讓 GetMessage 提早回傳 nullptr(需在 plugin 與 C# 端一致)。
+
+完成 1、2 後建議在編輯器下重現:載入 `https://www.interserv.com.tw/#service`,等待卡住或載入中時按停止,確認是否還會當機並視情況再套用 3。
diff --git a/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_en.md b/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_en.md
new file mode 100644
index 00000000..d8b70a54
--- /dev/null
+++ b/plugins/Windows/PlanDocument/webview_stuck_and_destroy_crash_fix_plan_en.md
@@ -0,0 +1,77 @@
+---
+name: WebView freeze and destroy crash fix
+overview: Based on crash reports, when loading specific pages (such as interserv.com.tw) in the Windows Editor and then pressing Stop, Unity crashes during the Destroy flow inside WebView2 (EmbeddedBrowserWebView.dll). The root cause is related to destroying the WebView while it is still loading/busy and race conditions between the main thread and the STA thread. We can mitigate this by stopping navigation before Destroy, avoiding C# calls to GetMessage during Destroy, and optionally handling timeouts more safely.
+todos: []
+isProject: false
+---
+
+## WebView freeze and crash after pressing Stop – fix plan
+
+### Problem summary
+
+- **Symptom**: In the Windows Editor, when loading `https://www.interserv.com.tw/#service`, the WebView tends to freeze, and Unity crashes after the user presses Stop.
+- **Crash location**: `EmbeddedBrowserWebView.dll` in `GetHandleVerifier` (exception_code 0x80000003). The call stack includes `CWebViewPlugin_Destroy` and `CWebViewPlugin_GetMessage`.
+- **Suspected causes**:
+ 1. The page is heavy (lots of scripts/resources and hash navigation). WebView2 is destroyed while still "loading" or busy, leading to unstable internal COM state and a crash during release.
+ 2. During Destroy, the main thread may still call `GetMessage`/Update in the same frame, causing a race with Destroy.
+ 3. If the STA thread is stuck in WebView2 (e.g. navigation or scripts not finished), `WM_WEBVIEW_DESTROY` cannot be processed in time. After the 10s timeout, the main thread may still remove the instance from `s_instances`, which can lead to use-after-free or bad COM release order when the STA thread eventually processes the message.
+
+### 1. In `WM_WEBVIEW_DESTROY`, stop navigation before releasing COM (core)
+
+**File**: `plugins/Windows/WebViewPlugin.cpp`
+
+Before setting `controller` / `compositionController` / `webview` to `nullptr`:
+
+- If `inst->webview` is valid, call `inst->webview->Stop()` first so WebView2 cancels ongoing navigation and attempts to leave the busy state.
+- Optional: call `Navigate(L"about:blank")` afterwards to clear the content. (Stopping and then synchronously waiting may complicate things; we can start by only calling `Stop`.)
+
+This reduces the chance that destroying WebView2 "while loading" triggers an internal check failure (such as in `GetHandleVerifier`).
+
+**Code location**: Around lines 353–368, inside `case WM_WEBVIEW_DESTROY`. Add the `inst->webview->Stop()` call before `inst->controller = nullptr`, and ensure `inst` is checked for null.
+
+### 2. C# `OnDestroy`: clear `webView` before calling Destroy (avoid race)
+
+**File**: for example `plugins/WebViewObject.cs` (or the corresponding file under `dist/package`)
+
+In the `UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN` branch:
+
+- If `webView != IntPtr.Zero`, first copy the pointer into a local variable, then **immediately** set `webView` to `IntPtr.Zero`, and finally call `_CWebViewPlugin_Destroy(savedPtr)`.
+- With this pattern, even if `Update` still runs in the same frame, it will see `webView == IntPtr.Zero` and stop calling `GetMessage`/Update/Render. This prevents managed code from entering the native layer or WebView2 paths while Destroy is in progress, reducing races with `EmbeddedBrowserWebView`.
+
+**Code location**: Around lines 836–845. Change it to:
+
+```csharp
+if (webView == IntPtr.Zero) return;
+var ptr = webView;
+webView = IntPtr.Zero;
+_CWebViewPlugin_Destroy(ptr);
+```
+
+(The rest of Destroy logic for textures, background, etc. stays unchanged.)
+
+### 3. On timeout, avoid removing from `s_instances` (optional, reduce UAF risk)
+
+**File**: `plugins/Windows/WebViewPlugin.cpp`
+
+Currently, `_CWebViewPlugin_Destroy` removes the instance from `s_instances` and releases it even when `WaitForSingleObject(destroyDoneEvent, 10000)` times out. If the STA thread later processes `WM_WEBVIEW_DESTROY`, it may access a deleted instance or hit a bad COM release order.
+
+- **Option A**: On timeout, do **not** remove the instance from `s_instances` (just log/ignore). This avoids the main thread freeing an instance that the STA thread may still use later. The downside is that the instance may leak until process exit.
+- **Option B**: Keep the current behavior but add comments documenting the risk, and rely on items 1 and 2 to make timeouts much less likely.
+
+Recommendation: implement 1 and 2 first. If crashes still occur, then consider Option A.
+
+### 4. Relationship between the URL and the "freeze"
+
+- `#service` is a front-end hash navigation target that may trigger heavy JS or dynamic loading, making WebView2 stay busy for longer.
+- The "freeze" may come from long-running scripts, multiple navigations/iframes, or slow `CapturePreview` on complex pages. All of these delay the STA thread from returning to the message loop. If the user presses Stop at this moment, Destroy is more likely to run while WebView2 is busy, so **stopping before releasing** becomes especially important.
+
+---
+
+### Recommended implementation order
+
+1. **Must-do**: In `WM_WEBVIEW_DESTROY`, call `inst->webview->Stop()` before releasing COM objects.
+2. **Must-do**: In C# `OnDestroy` (Windows), set `webView = IntPtr.Zero` before calling `_CWebViewPlugin_Destroy(ptr)`.
+3. **Optional**: If crashes still occur, consider not removing the instance from `s_instances` on Destroy timeout, or add a `destroying` flag so `GetMessage` returns `nullptr` early (requires changes in both the plugin and C# side).
+
+After implementing 1 and 2, test in the Editor by loading `https://www.interserv.com.tw/#service`, waiting until it freezes or is still loading, then pressing Stop. Verify whether the crash still happens and decide if step 3 is necessary.
+
diff --git a/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_cht.md b/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_cht.md
new file mode 100644
index 00000000..97ee767e
--- /dev/null
+++ b/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_cht.md
@@ -0,0 +1,73 @@
+---
+name: Windows WebView2 導覽與擷取管線修正
+overview: 快速切換網址或載入繁重頁面時, Unity 可能仍顯示舊貼圖、導覽未生效, 或僅有 CallOnStarted 而無 CallOnLoaded. Windows 外掛改為在新導覽前先停止進行中的導覽、將 Navigate 延後到下一輪訊息迴圈、在載入或導覽完成時解除 CapturePreview 鎖定, 並與 Destroy 時先 Stop 的行為一致.
+todos: []
+isProject: false
+---
+
+## Windows WebView2: 導覽與離屏擷取修正
+
+### 問題摘要
+
+- **換網址後畫面仍是舊頁**: Windows 上畫面來自 `CapturePreview` 的點陣圖. `_CWebViewPlugin_Update` 僅在 `captureInProgress` 為 false 時才 Post `WM_WEBVIEW_CAPTURE`. 若上一張 `CapturePreview` 的回呼延遲或未執行, 旗標可能一直為 true, Unity 仍畫舊幀, 即使新文件已觸發 `CallOnLoaded`.
+- **快速連續換網址**: 在同一個訊息處理裡對 `Stop()` 後立刻 `Navigate` 可能與 WebView2 競態: `Stop()` 未必同步完成, 下一個 `Navigate` 可能被忽略或看起來卡住.
+- **對同一 URL 重複 LoadURL**: 若已 `CallOnStarted` 卻始終沒有成功的 `CallOnLoaded`, 僅重送相同 URL 可能無法恢復; C# 端可能仍需 `about:blank` 再導向等作法.
+- **載入中 Destroy**: 釋放 COM 前先停止導覽 (其他計畫已述) 可降低繁重頁面崩潰; 一般載入路徑也採用先 `Stop` 再導向, 與該策略一致.
+
+**檔案**: `plugins/Windows/WebViewPlugin.cpp`
+
+---
+
+### 1. `NavigationCompleted`: 清除 `captureInProgress`
+
+**位置**: `add_NavigationCompleted` 回呼開頭.
+
+**原因**: Unity 端 `WebViewObject.Update` 以 `captureInProgress.exchange(true)` 決定是否 Post 擷取. 若旗標未清除, 不會再排 `CapturePreview`, 貼圖不會更新.
+
+**作法**: 在 `NavigationCompleted` 回呼一開始設 `inst->captureInProgress = false`, 讓每次導覽結束後下一幀的 `Update` 能再排新的擷取.
+
+**備註**: 理論上可能與尚未完成的舊 `CapturePreview` 重疊; 實務上可解決常見的貼圖卡死. 若需更嚴謹可再加世代序號忽略過期回呼.
+
+---
+
+### 2. `WM_WEBVIEW_LOAD_URL`: `Stop()`、清除擷取旗標、延遲 `Navigate`
+
+**位置**: `WndProc` 的 `case WM_WEBVIEW_LOAD_URL`.
+
+**原因**:
+
+- 新導覽前先取消上一段, 與 `WM_WEBVIEW_DESTROY` 的作法一致.
+- 明確換網址時清除 `captureInProgress`, 避免從繁重或異常頁面切走時擷取鎖卡住.
+- 以 **下一輪訊息迴圈** 執行 `Navigate` (`PostMessage` 且 `wParam == 1`), 讓 `Stop()` 有機會生效; 同一則訊息內立刻 `Navigate` 在忙碌頁面上可能被丟棄.
+
+**作法**:
+
+- `wParam == 0` (由 `_CWebViewPlugin_LoadURL` 送出): `Stop()`, `captureInProgress = false`, 再 `PostMessage(hwnd, WM_WEBVIEW_LOAD_URL, 1, (LPARAM)url)`, 此時不可 `delete[] url`.
+- `wParam == 1`: `Navigate(url)` 後 `delete[] url`.
+- 若 `inst` 或 `webview` 無效則 `delete[] url` 並 return, 避免洩漏.
+
+---
+
+### 3. `WM_WEBVIEW_LOAD_HTML`: `NavigateToString` 前先 `Stop()` 並清除 `captureInProgress`
+
+**位置**: `case WM_WEBVIEW_LOAD_HTML`.
+
+**原因**: 與 HTTP 載入相同, 先結束進行中導覽並解鎖擷取管線, 再換文件內容.
+
+**作法**: 在 `NavigateToString` 前呼叫 `Stop()` 並設 `captureInProgress = false`.
+
+---
+
+### 4. 與 Unity / C# 的關係 (本 C++ 檔案外)
+
+- Windows 上 `WebViewObject` 預設 `bitmapRefreshCycle = 10` 建議維持: iOS 等路徑較接近原生顯示, Windows 依賴 `CapturePreview` 離屏貼圖, 更新越頻繁越吃效能. 若仍覺畫面不夠順, 可視裝置與場景**自行調低**此值 (例如 `3`) 以換取較常重繪, 與上述原生修正可一併評估.
+- C# 可另做導覽監看 (逾時、`about:blank`、cache-busting) 處理僅有 `CallOnStarted` 而無 `CallOnLoaded` 的情況.
+
+---
+
+### 建議驗證
+
+1. Windows Editor 或 Standalone: 於多個 HTTPS 站之間快速切換 (含首頁較重的站).
+2. 確認最後一次要求的網址與 `CallOnLoaded`、畫面內容一致.
+3. 在慢載頁面完成前切到另一站, 確認不需多次手動重試即可看到新頁.
+4. 迴歸: 載入中 Destroy WebView, 確認無新崩潰 (若已套用其他 Destroy / C# `OnDestroy` 修正則一併驗證).
diff --git a/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_en.md b/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_en.md
new file mode 100644
index 00000000..a612bd8f
--- /dev/null
+++ b/plugins/Windows/PlanDocument/windows_webview2_navigation_capture_fix_plan_en.md
@@ -0,0 +1,75 @@
+---
+name: Windows WebView2 navigation and capture pipeline fixes
+overview: When switching URLs quickly or loading heavy pages, Unity could show stale textures, miss navigations, or get stuck with CallOnStarted without CallOnLoaded. The Windows plugin now stops in-flight navigation before starting a new one, defers Navigate to the next message pump, clears the CapturePreview gate when loading or when navigation completes, and aligns Destroy-time behavior with safer teardown.
+todos: []
+isProject: false
+---
+
+## Windows WebView2: navigation and offscreen capture fixes
+
+### Problem summary
+
+- **Stale texture after URL change**: On Windows, the visible page is a bitmap from `CapturePreview`. `_CWebViewPlugin_Update` only posts `WM_WEBVIEW_CAPTURE` when `captureInProgress` is false. If a previous `CapturePreview` completion is delayed or never runs, `captureInProgress` can stay true, so Unity keeps rendering the old frame even though `CallOnLoaded` already fired for the new document.
+- **Rapid URL switching**: Calling `Navigate` immediately after `Stop()` in the same message handler can race WebView2: `Stop()` is not always synchronous, so the next `Navigate` may be ignored or the view may appear stuck until the user retries.
+- **Repeated `LoadURL` to the same URL**: If navigation started (`CallOnStarted`) but never completed successfully (`CallOnLoaded`), resending the same URL from managed code may not recover; application-level workarounds (e.g. `about:blank` then target URL) may still be needed in C#.
+- **Destroy while loading**: Stopping navigation before releasing COM (already documented in related plans) reduces crashes on heavy pages; this change keeps `Stop()` before `Navigate` consistent with that idea for normal loads.
+
+**File**: `plugins/Windows/WebViewPlugin.cpp`
+
+---
+
+### 1. `NavigationCompleted`: clear `captureInProgress`
+
+**Location**: `add_NavigationCompleted` handler (early in the callback).
+
+**Reason**: Unity’s managed `WebViewObject.Update` uses `captureInProgress.exchange(true)` before posting a capture. If the flag never clears, no new `CapturePreview` is scheduled and the texture never updates for the new page.
+
+**Change**: At the beginning of the `NavigationCompleted` callback, set `inst->captureInProgress = false` so the next Unity `Update` can enqueue a fresh capture after each navigation finishes (success or failure path still advances the pipeline).
+
+**Note**: There is a theoretical race if two `CapturePreview` operations overlap; in practice this unblocks the common “stuck bitmap” case. If needed later, a generation counter could ignore stale completion callbacks.
+
+---
+
+### 2. `WM_WEBVIEW_LOAD_URL`: `Stop()`, clear capture flag, defer `Navigate`
+
+**Location**: `case WM_WEBVIEW_LOAD_URL` in `WndProc`.
+
+**Reason**:
+
+- Cancel the previous navigation before starting another, matching the pattern used in `WM_WEBVIEW_DESTROY`.
+- Clearing `captureInProgress` when explicitly loading a new URL avoids a stuck capture lock when switching away from a heavy or hung page.
+- Posting `Navigate` on the **next** message pump (`PostMessage` with `wParam == 1`) gives `Stop()` time to take effect; immediate `Navigate` in the same handler could be dropped on busy pages.
+
+**Change**:
+
+- `wParam == 0` (used by `_CWebViewPlugin_LoadURL`): `Stop()`, set `captureInProgress = false`, then `PostMessage(hwnd, WM_WEBVIEW_LOAD_URL, 1, (LPARAM)url)` without deleting `url` yet.
+- `wParam == 1`: `Navigate(url)`, then `delete[] url`.
+- If `inst` or `webview` is invalid, `delete[] url` and return (avoid leaks).
+
+`_CWebViewPlugin_LoadURL` continues to post with `wParam == 0` only.
+
+---
+
+### 3. `WM_WEBVIEW_LOAD_HTML`: `Stop()` and clear `captureInProgress` before `NavigateToString`
+
+**Location**: `case WM_WEBVIEW_LOAD_HTML`.
+
+**Reason**: Same as HTTP loads: cancel in-flight navigation and unlock the capture pipeline before replacing document content.
+
+**Change**: Before `NavigateToString`, call `Stop()` and set `captureInProgress = false`.
+
+---
+
+### 4. Relationship to Unity / C# side (out of scope for this C++ file)
+
+- Keep the default `bitmapRefreshCycle = 10` on Windows: unlike iOS-style paths that are closer to native rendering, Windows relies on `CapturePreview` offscreen bitmaps, and more frequent updates cost more CPU/GPU. If the texture still feels laggy, you may **lower** this value (e.g. to `3`) for smoother redraws at the expense of performance, and tune it together with these native fixes.
+- Managed code can add navigation watchdogs (timeouts, `about:blank` sandwich, cache-busting query strings) when `CallOnStarted` repeats without `CallOnLoaded`.
+
+---
+
+### Recommended verification
+
+1. Editor or standalone Windows: switch quickly among several HTTPS sites (including heavy homepages).
+2. Confirm `CallOnLoaded` and on-screen texture both match the last requested URL.
+3. From a slow-loading page, switch to another URL before load finishes; confirm the new page eventually appears without requiring multiple manual retries.
+4. Regression: destroy the WebView while a page is still loading; confirm no new crashes (together with existing Destroy / C# `OnDestroy` fixes if applied).
diff --git a/plugins/Windows/README.md b/plugins/Windows/README.md
new file mode 100644
index 00000000..e61fdf3a
--- /dev/null
+++ b/plugins/Windows/README.md
@@ -0,0 +1,79 @@
+# Windows WebView Plugin (WebView2)
+
+This folder contains the native Windows plugin for unity-webview, using **Microsoft WebView2** (Edge Chromium).
+
+## Requirements
+
+- Windows 10 or later
+- Visual Studio 2019 or 2022 (or newer) with "Desktop development with C++"
+- [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) (usually preinstalled on Windows 11 / recent Windows 10)
+
+## Building the DLL
+
+### 1. Get the WebView2 SDK (Required)
+
+The project requires the WebView2 SDK headers and libraries. **Please perform one of the following methods** before opening Visual Studio to build.
+
+**Method A – Restore via Script (Recommended)**
+
+Open **PowerShell** and navigate to the `plugins/Windows` folder, then run:
+
+```powershell
+.\restore_webview2_sdk.ps1
+```
+
+The script will automatically download `nuget.exe` (if not installed) and restore `Microsoft.Web.WebView2`, generating the `packages\Microsoft.Web.WebView2.1.0.3800.47\` folder which contains `build\native\include\WebView2.h` and other necessary files.
+
+**Method B – Manual NuGet Restore**
+
+If you have the [NuGet CLI](https://www.nuget.org/downloads) installed, open **Command Prompt** or **Developer PowerShell**, navigate to `plugins/Windows`, and run:
+
+```cmd
+nuget install Microsoft.Web.WebView2 -Version 1.0.3800.47 -OutputDirectory packages
+```
+
+**Method C – Manual SDK Download**
+
+1. Go to [NuGet: Microsoft.Web.WebView2](https://www.nuget.org/packages/Microsoft.Web.WebView2/), download the 1.0.3800.47 `.nupkg` file, rename its extension to `.zip`, and extract it.
+2. Place the extracted `build\native\include` and `build\native\x64` (use `build\native\x86` for 32-bit) in the appropriate paths, or modify the Include/Library directories in `WebViewPlugin.vcxproj` to point to your custom location.
+
+### 2. Build with Visual Studio
+
+1. **Ensure you have completed "1. Get the WebView2 SDK"** above, so that files like `packages\Microsoft.Web.WebView2.1.0.3800.47\build\native\include\WebView2.h` exist.
+2. Double-click to open **`WebViewPlugin.sln`** (or open this solution file via Visual Studio).
+3. **Build 64-bit (x64)**:
+ - Select **x64** from the platform dropdown at the top, and set the configuration to **Release**.
+ - Click "Build → Build Solution".
+ - The output DLL will be located at: `plugins\Windows\bin\x64\Release\WebView.dll`
+4. **Build 32-bit (Win32 / x86)**:
+ - Select **Win32** from the platform dropdown at the top, and set the configuration to **Release**.
+ - Click "Build → Build Solution".
+ - The output DLL will be located at: `plugins\Windows\bin\Win32\Release\WebView.dll`
+
+Command-line build (Optional):
+
+```cmd
+cd \plugins\Windows
+msbuild WebViewPlugin.sln /p:Configuration=Release /p:Platform=x64
+msbuild WebViewPlugin.sln /p:Configuration=Release /p:Platform=Win32
+```
+
+### 3. Use in Unity
+
+Copy the built `WebView.dll` into the corresponding directory in your Unity project:
+
+- 64-bit (x64): Copy to `Assets/Plugins/x64/WebView.dll`
+- 32-bit (Win32): Copy to `Assets/Plugins/x86/WebView.dll`
+
+Unity will load it on Windows Editor and Windows Standalone builds. The WebView2 **Runtime** must be installed on the machine where the built game runs (see the main README for distribution notes).
+
+## Debug Logging (Troubleshooting Mouse/Keyboard Input)
+
+The plugin has built-in `OutputDebugString` logging to verify if mouse/keyboard events reach the DLL and what the target window is.
+
+1. **Compile Time**: Logging is controlled by `WEBVIEW_DEBUG` at the top of `WebViewPlugin.cpp` (default is **0**, which means disabled). Change it to `#define WEBVIEW_DEBUG 1` and rebuild to enable it.
+2. **View Logs**:
+ - **DebugView** (Recommended): Download [Sysinternals DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview). Run it as **Administrator**, check **Capture → Capture Global Win32** in the menu, then run Unity and the sample scene. When you click on the WebView, you should see lines starting with `[WebView2]` (e.g., `SendMouseEvent called`, `MOUSE recv`, `targetClass=...`).
+ - **Visual Studio**: Run the Unity project using "Start Debugging" (or attach to the Unity process), and select "Debug" in the **Output** window to see the same logs.
+
+The logs will print: whether C# called `SendMouseEvent`/`SendKeyEvent`, if the STA thread received it, the handles for `hwnd`/`child`/`target`, the **class name** of the target window (e.g., `Chrome_WidgetWin_1` or `UnityWebView2Window`), and the converted coordinates. This information is crucial for determining if input forwarding is working correctly.
\ No newline at end of file
diff --git a/plugins/Windows/README_CHT.md b/plugins/Windows/README_CHT.md
new file mode 100644
index 00000000..e6c86e32
--- /dev/null
+++ b/plugins/Windows/README_CHT.md
@@ -0,0 +1,82 @@
+# Windows WebView Plugin (WebView2)
+
+This folder contains the native Windows plugin for unity-webview, using **Microsoft WebView2** (Edge Chromium).
+
+## Requirements
+
+- Windows 10 or later
+- Visual Studio 2019 or 2022 with "Desktop development with C++"
+- [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) (usually preinstalled on Windows 11 / recent Windows 10)
+
+## Building the DLL
+
+### 1. Get the WebView2 SDK(必做,否則會找不到 WebView2.h)
+
+專案需要 WebView2 SDK 的標頭檔與 lib,**請先執行下列其中一種方式**,再開 Visual Studio 建置。
+
+**方式 A – 用腳本還原(建議)**
+
+在 `plugins/Windows` 資料夾內,用 **PowerShell** 執行:
+
+```powershell
+.\restore_webview2_sdk.ps1
+```
+
+腳本會自動下載 nuget.exe(若尚未安裝)並還原 `Microsoft.Web.WebView2`,產生 `packages\Microsoft.Web.WebView2.1.0.3800.47\`,裡面會有 `build\native\include\WebView2.h` 等檔案。
+
+**方式 B – 手動用 NuGet 還原**
+
+若已安裝 [NuGet CLI](https://www.nuget.org/downloads),在 **命令提示字元** 或 **Developer PowerShell** 中切到 `plugins/Windows`,執行:
+
+```cmd
+cd 專案根目錄\plugins\Windows
+nuget install Microsoft.Web.WebView2 -Version 1.0.3800.47 -OutputDirectory packages
+```
+
+完成後應會出現 `packages\Microsoft.Web.WebView2.1.0.3800.47\build\native\include\WebView2.h`。
+
+**方式 C – 手動下載 SDK**
+
+1. 到 [NuGet: Microsoft.Web.WebView2](https://www.nuget.org/packages/Microsoft.Web.WebView2/) 下載 1.0.3800.47 的 .nupkg,副檔名改為 .zip 後解壓。
+2. 將解壓後的 `build\native\include` 與 `build\native\x64`(32 位元則用 `build\native\x86`)放到對應的位置,或在 `WebViewPlugin.vcxproj` 中把 Include/Lib 路徑改為指向你存放的地方。
+
+### 2. 用 Visual Studio 建置
+
+1. **確認已執行過上面的「1. Get the WebView2 SDK」**,使 `packages\Microsoft.Web.WebView2.1.0.3800.47\build\native\include\WebView2.h` 等檔案存在。
+2. 雙擊開啟 `WebViewPlugin.sln`(或用 Visual Studio 開啟此方案檔)。
+3. **建置 64 位元 (x64)**:
+ - 上方平台下拉選單選 **x64**,組態選 **Release**。
+ - 點擊「建置 → 建置方案」。
+ - 產出的 DLL 位於:`plugins\Windows\bin\x64\Release\WebView.dll`
+4. **建置 32 位元 (Win32 / x86)**:
+ - 上方平台下拉選單選 **Win32**,組態選 **Release**。
+ - 點擊「建置 → 建置方案」。
+ - 產出的 DLL 位於:`plugins\Windows\bin\Win32\Release\WebView.dll`
+
+命令列建置(可選):
+
+```cmd
+cd 專案根目錄\plugins\Windows
+msbuild WebViewPlugin.sln /p:Configuration=Release /p:Platform=x64
+msbuild WebViewPlugin.sln /p:Configuration=Release /p:Platform=Win32
+```
+
+### 3. Use in Unity
+
+將建置出的 `WebView.dll` 複製到你的 Unity 專案內對應的資料夾:
+
+- 64-bit (x64): 複製到 `Assets/Plugins/x64/WebView.dll`
+- 32-bit (Win32): 複製到 `Assets/Plugins/x86/WebView.dll`
+
+Unity will load it on Windows Editor and Windows Standalone builds. The WebView2 **Runtime** must be installed on the machine where the built game runs (see main README for distribution notes).
+
+## Debug 日誌(排查滑鼠/鍵盤無法操作)
+
+外掛內建 `OutputDebugString` 日誌,可用來確認滑鼠/鍵盤是否有進到 DLL、以及目標視窗為何。
+
+1. **編譯時**:預設已開啟(`WebViewPlugin.cpp` 頂端 `WEBVIEW_DEBUG` 為 1)。若要關閉,改為 `#define WEBVIEW_DEBUG 0` 後重新建置。
+2. **查看日誌**:
+ - **DebugView**(建議):下載 [Sysinternals DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview),以**系統管理員**執行,選單 **Capture → Capture Global Win32** 打勾,再執行 Unity 與範例場景,點擊 WebView 時應會看到 `[WebView2]` 開頭的行(例如 `SendMouseEvent called`、`MOUSE recv`、`targetClass=...`)。
+ - **Visual Studio**:用「啟動偵錯」執行 Unity 專案(或附加到 Unity 程序),在 **輸出** 視窗選擇「偵錯」即可看到相同內容。
+
+日誌會印出:C# 是否呼叫了 `SendMouseEvent`/`SendKeyEvent`、STA 執行緒是否收到、`hwnd`/`child`/`target` 視窗代碼、目標視窗的 **類別名稱**(例如 `Chrome_WidgetWin_1` 或 `UnityWebView2Window`)、以及轉換後的座標。若 `child` 為 null 或目標類別不對,可據此判斷是否需改用其他方式轉發輸入(例如 CompositionController 的 SendMouseInput)。
diff --git a/plugins/Windows/WebViewPlugin.cpp b/plugins/Windows/WebViewPlugin.cpp
new file mode 100644
index 00000000..0d7d94e7
--- /dev/null
+++ b/plugins/Windows/WebViewPlugin.cpp
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2012 GREE, Inc.
+ * Windows WebView2 implementation for unity-webview.
+ *
+ * This software is provided 'as-is'. See repository root LICENSE.
+ * Uses Microsoft WebView2 (Edge Chromium). Requires WebView2 Runtime.
+ */
+
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include
+#include