diff --git a/sample/Assets/Plugins.meta b/sample/Assets/Plugins.meta new file mode 100644 index 00000000..a772e932 --- /dev/null +++ b/sample/Assets/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3fab9ef1899dc4bf18586c6a01172d62 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/Android.meta b/sample/Assets/Plugins/Android.meta new file mode 100644 index 00000000..a55a2c27 --- /dev/null +++ b/sample/Assets/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dbc2200488e7d45189f9a082caf5d637 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl b/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl new file mode 100644 index 00000000..ef69fe9b Binary files /dev/null and b/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl differ diff --git a/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta b/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta new file mode 100644 index 00000000..18d15d70 --- /dev/null +++ b/sample/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 59b3f9343bdac42318926c7944365bb9 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl b/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl new file mode 100644 index 00000000..ed47788b Binary files /dev/null and b/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl differ diff --git a/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta b/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta new file mode 100644 index 00000000..83c2313f --- /dev/null +++ b/sample/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ea6387b3379b4458498b339bcd316c7c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/Android/core-1.6.0.aar.tmpl b/sample/Assets/Plugins/Android/core-1.6.0.aar.tmpl new file mode 100644 index 00000000..cdf16635 Binary files /dev/null and b/sample/Assets/Plugins/Android/core-1.6.0.aar.tmpl differ diff --git a/sample/Assets/Plugins/Android/core-1.6.0.aar.tmpl.meta b/sample/Assets/Plugins/Android/core-1.6.0.aar.tmpl.meta new file mode 100644 index 00000000..666d046e --- /dev/null +++ b/sample/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/sample/Assets/Plugins/Editor.meta b/sample/Assets/Plugins/Editor.meta new file mode 100644 index 00000000..4afc4293 --- /dev/null +++ b/sample/Assets/Plugins/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5415c2bc4488c42739b36767bc7f8c83 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs b/sample/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs new file mode 100644 index 00000000..19d9a961 --- /dev/null +++ b/sample/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs @@ -0,0 +1,539 @@ +#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_2018_1_OR_NEWER +using UnityEditor.Build; +#endif +using UnityEditor.Callbacks; +using UnityEditor; +using UnityEngine; + +#if 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 + public void OnPreprocessBuild(BuildTarget buildTarget, string path) { + 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 UNITYWEBVIEW_ANDROID_ENABLE_CAMERA + changed = (androidManifest.AddCamera() || changed); + changed = (androidManifest.AddGallery() || 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 UNITYWEBVIEW_ANDROID_ENABLE_CAMERA + changed = (androidManifest.AddCamera() || changed); + changed = (androidManifest.AddGallery() || 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.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.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.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.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/sample/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta b/sample/Assets/Plugins/Editor/UnityWebViewPostprocessBuild.cs.meta new file mode 100644 index 00000000..2d862b52 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebView.bundle.meta b/sample/Assets/Plugins/WebView.bundle.meta new file mode 100644 index 00000000..42a688b6 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebView.bundle/Contents.meta b/sample/Assets/Plugins/WebView.bundle/Contents.meta new file mode 100644 index 00000000..5839b4ea --- /dev/null +++ b/sample/Assets/Plugins/WebView.bundle/Contents.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee2bc92b52f924630bfd4aff89395583 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/WebView.bundle/Contents/Info.plist b/sample/Assets/Plugins/WebView.bundle/Contents/Info.plist new file mode 100644 index 00000000..32b46465 --- /dev/null +++ b/sample/Assets/Plugins/WebView.bundle/Contents/Info.plist @@ -0,0 +1,48 @@ + + + + + BuildMachineOSBuild + 23H124 + 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 + + DTPlatformName + macosx + DTPlatformVersion + 14.5 + DTSDKBuild + 23F73 + DTSDKName + macosx14.5 + DTXcode + 1540 + DTXcodeBuild + 15F31d + LSMinimumSystemVersion + 10.13 + + diff --git a/sample/Assets/Plugins/WebView.bundle/Contents/MacOS.meta b/sample/Assets/Plugins/WebView.bundle/Contents/MacOS.meta new file mode 100644 index 00000000..402df322 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView b/sample/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView new file mode 100755 index 00000000..7646c0d2 Binary files /dev/null and b/sample/Assets/Plugins/WebView.bundle/Contents/MacOS/WebView differ diff --git a/sample/Assets/Plugins/WebView.bundle/Contents/Resources.meta b/sample/Assets/Plugins/WebView.bundle/Contents/Resources.meta new file mode 100644 index 00000000..f28fb9be --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings b/sample/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings new file mode 100644 index 00000000..5e45963c Binary files /dev/null and b/sample/Assets/Plugins/WebView.bundle/Contents/Resources/InfoPlist.strings differ diff --git a/sample/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta b/sample/Assets/Plugins/WebView.bundle/Contents/_CodeSignature.meta new file mode 100644 index 00000000..3a521ad0 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources b/sample/Assets/Plugins/WebView.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..f4d2e431 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/WebViewObject.cs b/sample/Assets/Plugins/WebViewObject.cs new file mode 100644 index 00000000..01c826e0 --- /dev/null +++ b/sample/Assets/Plugins/WebViewObject.cs @@ -0,0 +1,1689 @@ +/* + * 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_OSX || UNITY_STANDALONE_OSX +using System.IO; +using System.Text.RegularExpressions; +using UnityEngine.EventSystems; +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 +{ + 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; + 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 + + 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); + } + } + + /// 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 (!Permission.HasUserAuthorizedPermission(Permission.Camera)) + { + permissions.Add(Permission.Camera); + } + 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 + } +#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_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 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_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 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_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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return true; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + Debug.LogError("Webview is not supported on this platform."); +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //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); +#endif + } + + public void SetMargins(int left, int top, int right, int bottom, bool relative = false) + { +#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + 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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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 + if (bg != null) + { + bg.gameObject.active = v; + } +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_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_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 SetAlertDialogEnabled(bool e) + { +#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX + // TODO: UNSUPPORTED +#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_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_IPHONE + // TODO: UNSUPPORTED +#elif UNITY_ANDROID + if (webView == null) + return; + webView.Call("SetCameraAccess", allowed); +#else + // TODO: UNSUPPORTED +#endif + } + + public void SetMicrophoneAccess(bool allowed) + { +#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX + // TODO: UNSUPPORTED +#elif UNITY_IPHONE + // TODO: UNSUPPORTED +#elif UNITY_ANDROID + if (webView == null) + return; + webView.Call("SetMicrophoneAccess", allowed); +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return false; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return 0; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return false; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return false; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED + return null; +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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 ClearCookies() + { +#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 || 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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER + //TODO: UNSUPPORTED +#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 (hasFocus) { + inputString += Input.inputString; + } + 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) { + 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.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); + } + } + + 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: + hasFocus = rect.Contains(Input.mousePosition); + break; + } + switch (Event.current.type) { + case EventType.MouseMove: + case EventType.MouseDown: + case EventType.MouseDrag: + case EventType.MouseUp: + case EventType.ScrollWheel: + if (hasFocus) { + Vector3 p; + p.x = Input.mousePosition.x - rect.x; + p.y = Input.mousePosition.y - rect.y; + { + int mouseState = 0; + if (Input.GetButtonDown("Fire1")) { + mouseState = 1; + } else if (Input.GetButton("Fire1")) { + mouseState = 2; + } else if (Input.GetButtonUp("Fire1")) { + mouseState = 3; + } + //_CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, Input.GetAxis("Mouse ScrollWheel"), mouseState); + _CWebViewPlugin_SendMouseEvent(webView, (int)p.x, (int)p.y, Input.mouseScrollDelta.y, 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) { + Vector3 p; + p.x = Input.mousePosition.x - rect.x; + p.y = Input.mousePosition.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/sample/Assets/Plugins/WebViewObject.cs.meta b/sample/Assets/Plugins/WebViewObject.cs.meta new file mode 100644 index 00000000..32948634 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/iOS.meta b/sample/Assets/Plugins/iOS.meta new file mode 100644 index 00000000..352898e8 --- /dev/null +++ b/sample/Assets/Plugins/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a53a54acdc5d64291aa49766bb494025 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/Plugins/iOS/WebView.mm b/sample/Assets/Plugins/iOS/WebView.mm new file mode 100644 index 00000000..4620012e --- /dev/null +++ b/sample/Assets/Plugins/iOS/WebView.mm @@ -0,0 +1,1218 @@ +/* + * 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 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]; + 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); \ + } \ +}; \ +"; + if (!zoom) { + str = [str stringByAppendingString:@"\ +(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]; + + 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"]; + } + 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)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) { + [[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]) { + [[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]); + 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)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_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_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_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_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/sample/Assets/Plugins/iOS/WebView.mm.meta b/sample/Assets/Plugins/iOS/WebView.mm.meta new file mode 100644 index 00000000..fb91d132 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/iOS/WebViewWithUIWebView.mm b/sample/Assets/Plugins/iOS/WebViewWithUIWebView.mm new file mode 100644 index 00000000..1f288658 --- /dev/null +++ b/sample/Assets/Plugins/iOS/WebViewWithUIWebView.mm @@ -0,0 +1,1303 @@ +/* + * 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 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]; + 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); \ + } \ +}; \ +"; + if (!zoom) { + str = [str stringByAppendingString:@"\ +(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)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) { + [[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) { + [[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]) { + [[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]); + 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)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_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_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_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_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/sample/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta b/sample/Assets/Plugins/iOS/WebViewWithUIWebView.mm.meta new file mode 100644 index 00000000..f5e0fb85 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/unity-webview-webgl-plugin.jslib b/sample/Assets/Plugins/unity-webview-webgl-plugin.jslib new file mode 100644 index 00000000..95777383 --- /dev/null +++ b/sample/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/sample/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta b/sample/Assets/Plugins/unity-webview-webgl-plugin.jslib.meta new file mode 100644 index 00000000..2e24b029 --- /dev/null +++ b/sample/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/sample/Assets/WebGLTemplates.meta b/sample/Assets/WebGLTemplates.meta new file mode 100644 index 00000000..1df8df3d --- /dev/null +++ b/sample/Assets/WebGLTemplates.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 396d2c966866e4a8ca47369a69d03109 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebGLTemplates/unity-webview-2020.meta b/sample/Assets/WebGLTemplates/unity-webview-2020.meta new file mode 100644 index 00000000..756a586c --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview-2020.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d9e103622e8c14154a1cd918fb92795e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebGLTemplates/unity-webview-2020/index.html b/sample/Assets/WebGLTemplates/unity-webview-2020/index.html new file mode 100644 index 00000000..84f13023 --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview-2020/index.html @@ -0,0 +1,108 @@ + + + + + + Unity WebGL Player | {{{ PRODUCT_NAME }}} + + + + + + +
+ +
+ +
+
+
+
+ +
+ + + diff --git a/sample/Assets/WebGLTemplates/unity-webview-2020/index.html.meta b/sample/Assets/WebGLTemplates/unity-webview-2020/index.html.meta new file mode 100644 index 00000000..512f42c4 --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview-2020/index.html.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7be04fa587d934a5c958c8fc02a10c40 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js b/sample/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js new file mode 100644 index 00000000..6a9c777f --- /dev/null +++ b/sample/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/sample/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta b/sample/Assets/WebGLTemplates/unity-webview-2020/unity-webview.js.meta new file mode 100644 index 00000000..8ee7ab8c --- /dev/null +++ b/sample/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/sample/Assets/WebGLTemplates/unity-webview.meta b/sample/Assets/WebGLTemplates/unity-webview.meta new file mode 100644 index 00000000..7c3eabcf --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74f24ca1a6cc14b5c8da7e2a8e5de817 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebGLTemplates/unity-webview/index.html b/sample/Assets/WebGLTemplates/unity-webview/index.html new file mode 100644 index 00000000..7474f1c4 --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview/index.html @@ -0,0 +1,32 @@ + + + + + Unity Web Player + + + + + + + + + + +
+
+
+ +
+
+ + + + + diff --git a/sample/Assets/WebGLTemplates/unity-webview/index.html.meta b/sample/Assets/WebGLTemplates/unity-webview/index.html.meta new file mode 100644 index 00000000..5b8b18d1 --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview/index.html.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cd45543727a7e47d88051ca9ab86a6f5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebGLTemplates/unity-webview/unity-webview.js b/sample/Assets/WebGLTemplates/unity-webview/unity-webview.js new file mode 100644 index 00000000..64f473c0 --- /dev/null +++ b/sample/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/sample/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta b/sample/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta new file mode 100644 index 00000000..b421d598 --- /dev/null +++ b/sample/Assets/WebGLTemplates/unity-webview/unity-webview.js.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8de1ecd3ea5954800b53548d8c2e2d70 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebPlayerTemplates.meta b/sample/Assets/WebPlayerTemplates.meta new file mode 100644 index 00000000..d1d66623 --- /dev/null +++ b/sample/Assets/WebPlayerTemplates.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1ec4661d3c10c4d0e8b5e54ce1e46aa5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebPlayerTemplates/unity-webview.meta b/sample/Assets/WebPlayerTemplates/unity-webview.meta new file mode 100644 index 00000000..06794cb6 --- /dev/null +++ b/sample/Assets/WebPlayerTemplates/unity-webview.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e76c9a30b7f6447eca50079ce4d595ed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebPlayerTemplates/unity-webview/index.html b/sample/Assets/WebPlayerTemplates/unity-webview/index.html new file mode 100644 index 00000000..511e1137 --- /dev/null +++ b/sample/Assets/WebPlayerTemplates/unity-webview/index.html @@ -0,0 +1,136 @@ + + + + + Unity Web Player | %UNITY_WEB_NAME% + %UNITY_UNITYOBJECT_DEPENDENCIES% + + + + + + +

Unity Web Player | %UNITY_WEB_NAME%

%UNITY_BETA_WARNING% +
+
+
+ + Unity Web Player. Install now! + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/sample/Assets/WebPlayerTemplates/unity-webview/index.html.meta b/sample/Assets/WebPlayerTemplates/unity-webview/index.html.meta new file mode 100644 index 00000000..fcb667ad --- /dev/null +++ b/sample/Assets/WebPlayerTemplates/unity-webview/index.html.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f6333062aa8e346f2abc78ef7a457580 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png b/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png new file mode 100644 index 00000000..773c2e2d Binary files /dev/null and b/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png differ diff --git a/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta b/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta new file mode 100644 index 00000000..b1c973ba --- /dev/null +++ b/sample/Assets/WebPlayerTemplates/unity-webview/thumbnail.png.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 018355354713f41b2bed252c88e082c7 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Assets/WebPlayerTemplates/unity-webview/unity-webview.js b/sample/Assets/WebPlayerTemplates/unity-webview/unity-webview.js new file mode 100644 index 00000000..f2ebe5fb --- /dev/null +++ b/sample/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/sample/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta b/sample/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta new file mode 100644 index 00000000..ac69546e --- /dev/null +++ b/sample/Assets/WebPlayerTemplates/unity-webview/unity-webview.js.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7bf88e2aa1e624d64b530ad0c2383b9e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sample/Packages/manifest.json b/sample/Packages/manifest.json new file mode 100644 index 00000000..83493a02 --- /dev/null +++ b/sample/Packages/manifest.json @@ -0,0 +1,36 @@ +{ + "dependencies": { + "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/sample/Packages/packages-lock.json b/sample/Packages/packages-lock.json new file mode 100644 index 00000000..ed694280 --- /dev/null +++ b/sample/Packages/packages-lock.json @@ -0,0 +1,256 @@ +{ + "dependencies": { + "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.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/sample/ProjectSettings/MemorySettings.asset b/sample/ProjectSettings/MemorySettings.asset new file mode 100644 index 00000000..5b5facec --- /dev/null +++ b/sample/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/sample/ProjectSettings/PackageManagerSettings.asset b/sample/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 00000000..1a1de66f --- /dev/null +++ b/sample/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + 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_EnablePreReleasePackages: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_ConfigSource: 0 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -844 + m_OriginalInstanceId: -846 + m_LoadAssets: 0 diff --git a/sample/ProjectSettings/PresetManager.asset b/sample/ProjectSettings/PresetManager.asset new file mode 100644 index 00000000..67a94dae --- /dev/null +++ b/sample/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/sample/ProjectSettings/ProjectSettings.asset b/sample/ProjectSettings/ProjectSettings.asset index ed70c84c..619d72f9 100644 --- a/sample/ProjectSettings/ProjectSettings.asset +++ b/sample/ProjectSettings/ProjectSettings.asset @@ -3,9 +3,11 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 26 productGUID: 17d7a4b3e162f44209a850d2bb945e86 AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 @@ -14,7 +16,7 @@ PlayerSettings: productName: sample defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} - m_SplashScreenBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21176471, a: 1} + m_SplashScreenBackgroundColor: {r: 0.12156863, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 @@ -38,8 +40,6 @@ PlayerSettings: width: 1 height: 1 m_SplashScreenLogos: [] - m_SplashScreenBackgroundLandscape: {fileID: 0} - m_SplashScreenBackgroundPortrait: {fileID: 0} m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 @@ -48,39 +48,55 @@ PlayerSettings: defaultScreenHeightWeb: 320 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 - m_MobileMTRendering: 0 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 - tizenShowActivityIndicatorOnLoading: -1 - iosAppInBackgroundBehavior: 0 - displayResolutionDialog: 1 - iosAllowHTTPDownload: 1 + iosUseCustomAppBackgroundBehavior: 0 allowedAutorotateToPortrait: 0 allowedAutorotateToPortraitUpsideDown: 0 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 0 + preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 - defaultIsFullScreen: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 defaultIsNativeResolution: 1 + macRetinaSupport: 1 runInBackground: 0 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 + dedicatedServerOptimizations: 0 bakeCollisionMeshes: 0 forceSingleInstance: 0 + useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 - graphicsJobs: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 @@ -88,68 +104,77 @@ PlayerSettings: xboxEnableFitness: 0 visibleInBackground: 0 allowFullscreenSwitch: 1 - graphicsJobMode: 0 - macFullscreenMode: 2 - d3d9FullscreenMode: 1 - d3d11FullscreenMode: 1 + fullscreenMode: 2 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 - n3dsDisableStereoscopicView: 0 - n3dsEnableSharedListOpt: 1 - n3dsEnableVSync: 0 - ignoreAlphaClear: 0 + metalFramebufferOnly: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 - videoMemoryForVertexBuffers: 0 - psp2PowerMode: 0 - psp2AcquireBGM: 1 - wiiUTVResolution: 0 - wiiUGamePadMSAA: 1 - wiiUSupportsNunchuk: 0 - wiiUSupportsClassicController: 0 - wiiUSupportsBalanceBoard: 0 - wiiUSupportsMotionPlus: 0 - wiiUSupportsProController: 0 - wiiUAllowScreenCapture: 1 - wiiUControllerCount: 0 - m_SupportedAspectRatios: - 4:3: 1 - 5:4: 1 - 16:10: 1 - 16:9: 1 - Others: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchNVNGraphicsFirmwareMemory: 32 + switchMaxWorkerMultiple: 8 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 0 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 bundleVersion: 1.0 preloadedAssets: [] metroInputSource: 0 + wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 0 xboxOneEnable7thCore: 0 vrSettings: - cardboard: - depthFormat: 0 - enableTransitionView: 0 - daydream: - depthFormat: 0 - useSustainedPerformanceMode: 0 - hololens: - depthFormat: 1 - protectGraphicsMemory: 0 + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 applicationIdentifier: Android: net.gree.webview.sample Standalone: unity.Sample Inc..sample Tizen: net.gree.webview.sample - iOS: net.gree.webview.sample + iPhone: net.gree.webview.sample tvOS: net.gree.webview.sample buildNumber: - iOS: 0 + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 16 + AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: @@ -162,37 +187,27 @@ PlayerSettings: APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 0 - VertexChannelCompressionMask: - serializedVersion: 2 - m_Bits: 238 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 214 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 6.0 + iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 9.0 + tvOSTargetOSVersionString: 12.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 - iPhoneSplashScreen: {fileID: 0} - iPhoneHighResSplashScreen: {fileID: 0} - iPhoneTallHighResSplashScreen: {fileID: 0} - iPhone47inSplashScreen: {fileID: 0} - iPhone55inPortraitSplashScreen: {fileID: 0} - iPhone55inLandscapeSplashScreen: {fileID: 0} - iPhone58inPortraitSplashScreen: {fileID: 0} - iPhone58inLandscapeSplashScreen: {fileID: 0} - iPadPortraitSplashScreen: {fileID: 0} - iPadHighResPortraitSplashScreen: {fileID: 0} - iPadLandscapeSplashScreen: {fileID: 0} - iPadHighResLandscapeSplashScreen: {fileID: 0} appleTVSplashScreen: {fileID: 0} appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] @@ -214,31 +229,65 @@ PlayerSettings: iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] + macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 0 metalAPIValidation: 1 + metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 1 + iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 - AndroidTargetDevice: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 00000000000000000000000000000000 + templatePackageId: + templateDefaultScene: + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 1 + AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} - AndroidKeystoreName: + AndroidKeystoreName: '{inproject}: ' AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 1 AndroidIsGame: 1 + AndroidEnableTango: 0 androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 - resolutionDialogBanner: {fileID: 0} + chromeosInputEmulation: 1 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 200 m_BuildTargetIcons: - m_BuildTarget: m_Icons: @@ -247,41 +296,294 @@ PlayerSettings: m_Width: 128 m_Height: 128 m_Kind: 0 + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - 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: 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_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_BuildTargetBatching: [] + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: + - m_BuildTarget: WindowsStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: MacStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: LinuxStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AndroidPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: iOSSupport + m_GraphicsJobs: 0 + - m_BuildTarget: PS4Player + m_GraphicsJobs: 0 + - m_BuildTarget: PS5Player + m_GraphicsJobs: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobs: 0 + - m_BuildTarget: GameCoreXboxOneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: GameCoreScarlettSupport + m_GraphicsJobs: 0 + - m_BuildTarget: Switch + m_GraphicsJobs: 0 + - m_BuildTarget: WebGLSupport + m_GraphicsJobs: 0 + - m_BuildTarget: MetroSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AppleTVSupport + m_GraphicsJobs: 0 + - m_BuildTarget: VisionOSPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: BJMSupport + m_GraphicsJobs: 0 + - m_BuildTarget: CloudRendering + m_GraphicsJobs: 0 + - m_BuildTarget: EmbeddedLinux + m_GraphicsJobs: 0 + - m_BuildTarget: QNX + m_GraphicsJobs: 0 + m_BuildTargetGraphicsJobMode: + - m_BuildTarget: PS4Player + m_GraphicsJobMode: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobMode: 0 m_BuildTargetGraphicsAPIs: - m_BuildTarget: WindowsStandaloneSupport - m_APIs: 01000000 - m_Automatic: 0 + m_APIs: 02000000 + m_Automatic: 1 - m_BuildTarget: AndroidPlayer m_APIs: 08000000 m_Automatic: 0 - m_BuildTarget: MacStandaloneSupport m_APIs: 11000000 m_Automatic: 0 + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 m_BuildTargetVRSettings: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 - webPlayerTemplate: PROJECT:unity-webview + openGLRequireES32: 0 m_TemplateCustomTags: {} - wiiUTitleID: 0005000011000000 - wiiUGroupID: 00010000 - wiiUCommonSaveSize: 4096 - wiiUAccountSaveSize: 2048 - wiiUOlvAccessKey: 0 - wiiUTinCode: 0 - wiiUJoinGameId: 0 - wiiUJoinGameModeMask: 0000000000000000 - wiiUCommonBossSize: 0 - wiiUAccountBossSize: 0 - wiiUAddOnUniqueIDs: [] - wiiUMainThreadStackSize: 3072 - wiiULoaderThreadStackSize: 1024 - wiiUSystemHeapSize: 128 - wiiUTVStartupScreen: {fileID: 0} - wiiUGamePadStartupScreen: {fileID: 0} - wiiUDrcBufferDisabled: 0 - wiiUProfilerLibPath: + mobileMTRendering: + VisionOS: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 1 + - m_BuildTarget: XboxOne + m_EncodingQuality: 1 + - m_BuildTarget: PS4 + m_EncodingQuality: 1 + - m_BuildTarget: GameCoreScarlett + m_EncodingQuality: 1 + - m_BuildTarget: GameCoreXboxOne + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 2 + - m_BuildTarget: XboxOne + m_EncodingQuality: 2 + - m_BuildTarget: PS4 + m_EncodingQuality: 2 + - m_BuildTarget: GameCoreScarlett + m_EncodingQuality: 2 + - m_BuildTarget: GameCoreXboxOne + m_EncodingQuality: 2 + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 @@ -289,14 +591,20 @@ PlayerSettings: cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 10.13.0 + switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: + switchCompilerFlags: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: @@ -312,6 +620,7 @@ PlayerSettings: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: + switchTitleNames_15: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: @@ -327,6 +636,7 @@ PlayerSettings: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: + switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} @@ -342,6 +652,7 @@ PlayerSettings: 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} @@ -357,6 +668,7 @@ PlayerSettings: switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: @@ -366,7 +678,6 @@ PlayerSettings: switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 - switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: @@ -388,6 +699,7 @@ PlayerSettings: switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 + switchRatingsInt_12: 0 switchLocalCommunicationIds_0: switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: @@ -399,8 +711,15 @@ PlayerSettings: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 3 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 1 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 @@ -411,7 +730,12 @@ PlayerSettings: switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 - switchPlayerConnectionEnabled: 1 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -430,12 +754,15 @@ PlayerSettings: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: @@ -448,17 +775,20 @@ PlayerSettings: ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 ps4Passcode: 5xr84P2R391UXaLHbavJvFZGfO47XWS2 - ps4UseDebugIl2cppLibs: 0 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: 3 ps4Audio3dVirtualSpeakerCount: 14 @@ -475,63 +805,21 @@ PlayerSettings: ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 monoEnv: - psp2Splashimage: {fileID: 0} - psp2NPTrophyPackPath: - psp2NPSupportGBMorGJP: 0 - psp2NPAgeRating: 12 - psp2NPTitleDatPath: - psp2NPCommsID: - psp2NPCommunicationsID: - psp2NPCommsPassphrase: - psp2NPCommsSig: - psp2ParamSfxPath: - psp2ManualPath: - psp2LiveAreaGatePath: - psp2LiveAreaBackroundPath: - psp2LiveAreaPath: - psp2LiveAreaTrialPath: - psp2PatchChangeInfoPath: - psp2PatchOriginalPackage: - psp2PackagePassword: qVOw5lxuBEBNue7b9PZS0hoI6pgabi9U - psp2KeystoneFile: - psp2MemoryExpansionMode: 0 - psp2DRMType: 0 - psp2StorageType: 0 - psp2MediaCapacity: 0 - psp2DLCConfigPath: - psp2ThumbnailPath: - psp2BackgroundPath: - psp2SoundPath: - psp2TrophyCommId: - psp2TrophyPackagePath: - psp2PackagedResourcesPath: - psp2SaveDataQuota: 10240 - psp2ParentalLevel: 1 - psp2ShortTitle: Not Set - psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF - psp2Category: 0 - psp2MasterVersion: 01.00 - psp2AppVersion: 01.00 - psp2TVBootMode: 0 - psp2EnterButtonAssignment: 2 - psp2TVDisableEmu: 0 - psp2AllowTwitterDialog: 1 - psp2Upgradable: 0 - psp2HealthWarning: 0 - psp2UseLibLocation: 0 - psp2InfoBarOnStartup: 0 - psp2InfoBarColor: 0 - psp2UseDebugIl2cppLibs: 0 - psmSplashimage: {fileID: 0} splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 256 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 webGLDataCaching: 0 webGLDebugSymbols: 0 webGLEmscriptenArgs: @@ -539,19 +827,53 @@ PlayerSettings: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 - webGLUseWasm: 0 webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 scriptingDefineSymbols: {} + additionalCompilerArguments: {} platformArchitecture: - iOS: 2 + iPhone: 1 scriptingBackend: Android: 0 Standalone: 0 WebGL: 1 - iOS: 1 + iPhone: 1 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + managedStrippingLevel: + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + QNX: 1 + Stadia: 1 + VisionOS: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 incrementalIl2cppBuild: - iOS: 0 + iPhone: 0 + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 @@ -565,46 +887,24 @@ PlayerSettings: metroApplicationDescription: sample wsaImages: {} metroTileShortName: - metroCommandLineArgsFile: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 1 metroTileBackgroundColor: {r: 0, g: 0, b: 0, a: 1} metroSplashScreenBackgroundColor: {r: 0, g: 0, b: 0, a: 1} metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 platformCapabilities: {} + metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: - metroCompilationOverrides: 1 - tizenProductDescription: - tizenProductURL: - tizenSigningProfileName: - tizenGPSPermissions: 0 - tizenMicrophonePermissions: 0 - tizenDeploymentTarget: - tizenDeploymentTargetType: -1 - tizenMinOSVersion: 1 - n3dsUseExtSaveData: 0 - n3dsCompressStaticMem: 1 - n3dsExtSaveDataNumber: 0x12345 - n3dsStackSize: 131072 - n3dsTargetPlatform: 2 - n3dsRegion: 7 - n3dsMediaSize: 0 - n3dsLogoStyle: 3 - n3dsTitle: GameName - n3dsProductCode: - n3dsApplicationId: 0xFF3FF - stvDeviceAddress: - stvProductDescription: - stvProductAuthor: - stvProductAuthorEmail: - stvProductLink: - stvProductCategory: 0 + vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: @@ -614,6 +914,7 @@ PlayerSettings: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: @@ -622,21 +923,44 @@ PlayerSettings: XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 XboxOneEnableGPUVariability: 0 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 - xboxOneScriptCompiler: 0 - vrEditorSettings: - daydream: - daydreamIconForeground: {fileID: 0} - daydreamIconBackground: {fileID: 0} + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} cloudServicesEnabled: {} - facebookSdkVersion: 7.9.1 - apiCompatibilityLevel: 2 + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 1 + hmiLogStartupTiming: 0 + hmiCpuConfiguration: + apiCompatibilityLevel: 6 + activeInputHandler: 0 + windowsGamepadBackendHint: 0 cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 - enableNewInputSystem: 0 + legacyClampBlendShapeWeights: 1 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 diff --git a/sample/ProjectSettings/ProjectVersion.txt b/sample/ProjectSettings/ProjectVersion.txt index 7a59bc87..8fb87e60 100644 --- a/sample/ProjectSettings/ProjectVersion.txt +++ b/sample/ProjectSettings/ProjectVersion.txt @@ -1 +1,2 @@ -m_EditorVersion: 5.6.7f1 +m_EditorVersion: 2022.3.50f1 +m_EditorVersionWithRevision: 2022.3.50f1 (c3db7f8bf9b1) diff --git a/sample/ProjectSettings/UnityConnectSettings.asset b/sample/ProjectSettings/UnityConnectSettings.asset index 2943e440..a88bee0f 100644 --- a/sample/ProjectSettings/UnityConnectSettings.asset +++ b/sample/ProjectSettings/UnityConnectSettings.asset @@ -3,27 +3,34 @@ --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 + serializedVersion: 1 m_Enabled: 0 m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: + 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/api/events/crashes + 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_InitializeOnStartup: 1 m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 m_TestMode: 0 - m_EnabledPlatforms: 4294967295 m_IosGameId: m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/sample/ProjectSettings/VFXManager.asset b/sample/ProjectSettings/VFXManager.asset new file mode 100644 index 00000000..852348bc --- /dev/null +++ b/sample/ProjectSettings/VFXManager.asset @@ -0,0 +1,18 @@ +%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_EmptyShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + m_MaxScrubTime: 30 + m_CompiledVersion: 0 + m_RuntimeVersion: 0 + m_RuntimeResources: {fileID: 0} + m_BatchEmptyLifetime: 300 diff --git a/sample/ProjectSettings/VersionControlSettings.asset b/sample/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 00000000..dca28814 --- /dev/null +++ b/sample/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