├── Assets ├── Plugins.meta └── Plugins │ ├── WebGLCachedXHRExtensions │ ├── Readme.txt.meta │ ├── CachedXHRExtensions.cs.meta │ ├── Readme.txt │ ├── CachedXHRExtensions.jslib │ ├── CachedXHRExtensions.jslib.meta │ ├── CachedXHRExtensions.jspre.meta │ ├── CachedXHRExtensions.jspre │ └── CachedXHRExtensions.cs │ ├── WebGLCachedXMLHttpRequest │ ├── readme.txt.meta │ ├── WebGLCachedXMLHttpRequest.jspre.meta │ ├── readme.txt │ └── WebGLCachedXMLHttpRequest.jspre │ ├── WebGLMemoryStats.meta │ ├── WebGLCachedXHRExtensions.meta │ ├── WebGLCachedXMLHttpRequest.meta │ └── WebGLMemoryStats │ ├── WebGLMemoryStats.cs.meta │ ├── WebGLMemoryStats.jslib.meta │ ├── WebGLMemoryStats.jslib │ └── WebGLMemoryStats.cs ├── .gitignore ├── LICENSE └── README.md /Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fc5efad0a9584470ad7afef88a0e016 3 | folderAsset: yes 4 | timeCreated: 1492699342 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/Readme.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 225f00f1c9a4140119d314735b4ff27a 3 | timeCreated: 1492197173 4 | licenseType: Free 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXMLHttpRequest/readme.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6989f2bb9aa5943f0b969d3f15ed1f96 3 | timeCreated: 1473845564 4 | licenseType: Store 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLMemoryStats.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18e35a3639fe64b109a0bcced8c037c3 3 | folderAsset: yes 4 | timeCreated: 1492699371 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6431ffe25c68f4c97a6d3b2849146da8 3 | folderAsset: yes 4 | timeCreated: 1492196494 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXMLHttpRequest.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0d5812f2e703243c1ac712fde4b81fbc 3 | folderAsset: yes 4 | timeCreated: 1473845563 5 | licenseType: Store 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLMemoryStats/WebGLMemoryStats.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9a61a8d1c2c1648b8a590c9f727ed999 3 | timeCreated: 1491486211 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba510e8181389488c912b6859fe89257 3 | timeCreated: 1492130611 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/Readme.txt: -------------------------------------------------------------------------------- 1 | This plugin provides additional functionality on top of the CachedXMLHttpRequest 2 | addon. The classes referred to below are in the Kongregate namespace. 3 | 4 | * Clearing the cache by calling CachedXHRExtensions.CleanCache() 5 | * Asynchronously querying the cache to determine if an item exists: 6 | IEnumerator CheckIfAssetExists() { 7 | var query = new CacheEntryQuery("https://whatever.io/file.xml"); 8 | yield return query; 9 | if (query.IsCached) { 10 | Debug.Log("Asset exists in cache!"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLMemoryStats/WebGLMemoryStats.jslib.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 41cee61de40a44f6ea5efd5a1b832c14 3 | timeCreated: 1491486042 4 | licenseType: Free 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | platformData: 11 | Any: 12 | enabled: 0 13 | settings: {} 14 | Editor: 15 | enabled: 0 16 | settings: 17 | DefaultValueInitialized: true 18 | WebGL: 19 | enabled: 1 20 | settings: {} 21 | userData: 22 | assetBundleName: 23 | assetBundleVariant: 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /[Pp]roject[Ss]ettings/ 7 | /Assets/AssetStoreTools* 8 | 9 | # Visual Studio 2015 cache directory 10 | /.vs/ 11 | 12 | # Autogenerated VS/MD/Consulo solution and project files 13 | ExportedObj/ 14 | .consulo/ 15 | *.csproj 16 | *.unityproj 17 | *.sln 18 | *.suo 19 | *.tmp 20 | *.user 21 | *.userprefs 22 | *.pidb 23 | *.booproj 24 | *.svd 25 | *.pdb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | 30 | # Unity3D Generated File On Crash Reports 31 | sysinfo.txt 32 | 33 | build.log 34 | *.unitypackage 35 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.jslib: -------------------------------------------------------------------------------- 1 | mergeInto(LibraryManager.library, { 2 | CachedXHRExtensions_SearchCache: function(url) { CachedXHRExtensions.searchCache(url); }, 3 | CachedXHRExtensions_CheckStatus: function(url) { return CachedXHRExtensions.checkStatus(url); }, 4 | CachedXHRExtensions_CleanCache: function() { 5 | try { 6 | var self = CachedXMLHttpRequest.cache; 7 | self.db.transaction([self.store], "readwrite").objectStore(self.store).clear().onerror = function(){ 8 | e.preventDefault(); 9 | }; 10 | } catch(e) {} 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.jslib.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b73b16a48571c4d8883d5c857f8bb913 3 | timeCreated: 1492131509 4 | licenseType: Free 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | platformData: 11 | Any: 12 | enabled: 0 13 | settings: {} 14 | Editor: 15 | enabled: 0 16 | settings: 17 | DefaultValueInitialized: true 18 | WebGL: 19 | enabled: 1 20 | settings: {} 21 | userData: 22 | assetBundleName: 23 | assetBundleVariant: 24 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.jspre.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae422c9d28d624d6f9abbed3bb5abfdc 3 | timeCreated: 1492271713 4 | licenseType: Free 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | platformData: 11 | Any: 12 | enabled: 0 13 | settings: {} 14 | Editor: 15 | enabled: 0 16 | settings: 17 | DefaultValueInitialized: true 18 | WebGL: 19 | enabled: 1 20 | settings: {} 21 | userData: 22 | assetBundleName: 23 | assetBundleVariant: 24 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXMLHttpRequest/WebGLCachedXMLHttpRequest.jspre.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 487aadcd197bf4f0a8c842ec4929673c 3 | timeCreated: 1473845564 4 | licenseType: Store 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | platformData: 11 | Any: 12 | enabled: 0 13 | settings: {} 14 | Editor: 15 | enabled: 0 16 | settings: 17 | DefaultValueInitialized: true 18 | WebGL: 19 | enabled: 1 20 | settings: {} 21 | userData: 22 | assetBundleName: 23 | assetBundleVariant: 24 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLMemoryStats/WebGLMemoryStats.jslib: -------------------------------------------------------------------------------- 1 | var MemoryStatsPlugin = { 2 | 3 | GetTotalMemorySize: function() { 4 | return TOTAL_MEMORY; // WebGLMemorySize in bytes 5 | }, 6 | 7 | GetTotalStackSize: function() { 8 | return TOTAL_STACK; 9 | }, 10 | 11 | GetStaticMemorySize: function() { 12 | return STATICTOP - STATIC_BASE; 13 | }, 14 | 15 | GetDynamicMemorySize: function() { 16 | if (typeof DYNAMICTOP !== "undefined") { 17 | return DYNAMICTOP - DYNAMIC_BASE; 18 | } else { 19 | // Unity 5.6+ 20 | return HEAP32[DYNAMICTOP_PTR >> 2] - DYNAMIC_BASE; 21 | } 22 | } 23 | }; 24 | 25 | mergeInto(LibraryManager.library, MemoryStatsPlugin); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kongregate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.jspre: -------------------------------------------------------------------------------- 1 | setTimeout(function(){ 2 | var enabled = typeof CachedXMLHttpRequest !== "undefined" && !!CachedXMLHttpRequest.cache; 3 | console.log("CachedXHRExtensions initialized, enabled=" + enabled); 4 | var CachedXHRExtensions = function() { 5 | this._cacheStates = {}; 6 | }; 7 | 8 | CachedXHRExtensions.prototype.searchCache = function(url) { 9 | if (!enabled) return; 10 | var self = this; 11 | url = typeof url === "string" ? url : Pointer_stringify(url); 12 | delete this._cacheStates[url]; 13 | 14 | CachedXMLHttpRequest.cache.get(url, function(err, result) { 15 | if (err || !result || !result.meta) { 16 | self._cacheStates[url] = false; 17 | } else { 18 | self._cacheStates[url] = true; 19 | } 20 | }); 21 | }; 22 | 23 | CachedXHRExtensions.prototype.checkStatus = function(url) { 24 | if (!enabled) return -1; 25 | 26 | url = typeof url === "string" ? url : Pointer_stringify(url); 27 | if (this._cacheStates[url] === undefined) return 0; 28 | return this._cacheStates[url] ? 1 : -1; 29 | }; 30 | 31 | window.CachedXHRExtensions = new CachedXHRExtensions(); 32 | }, 0); 33 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXHRExtensions/CachedXHRExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Kongregate { 5 | public class CacheEntryQuery : CustomYieldInstruction { 6 | private readonly string Url; 7 | private int Status = 0; 8 | 9 | public CacheEntryQuery(string url) { 10 | Url = url; 11 | CachedXHRExtensions_SearchCache(url); 12 | } 13 | 14 | public override bool keepWaiting { 15 | get { 16 | Status = CachedXHRExtensions_CheckStatus(Url); 17 | return Status == 0; 18 | } 19 | } 20 | 21 | public bool IsCached { 22 | get { 23 | if (Status == 0) { 24 | Debug.Log("CacheEntryQuery: returning IsCached=false since query is pending"); 25 | return false; 26 | } 27 | 28 | return Status == 1; 29 | } 30 | } 31 | 32 | #if UNITY_WEBGL && !UNITY_EDITOR 33 | [DllImport("__Internal")] 34 | private static extern void CachedXHRExtensions_SearchCache(string url); 35 | 36 | [DllImport("__Internal")] 37 | private static extern int CachedXHRExtensions_CheckStatus(string url); 38 | #else 39 | private static void CachedXHRExtensions_SearchCache(string url) { } 40 | private static int CachedXHRExtensions_CheckStatus(string url) { return -1; } 41 | #endif 42 | } 43 | 44 | public class CachedXHRExtensions { 45 | public static void CleanCache() { 46 | CachedXHRExtensions_CleanCache(); 47 | } 48 | 49 | #if UNITY_WEBGL && !UNITY_EDITOR 50 | [DllImport("__Internal")] 51 | static extern int CachedXHRExtensions_CleanCache(); 52 | #else 53 | static void CachedXHRExtensions_CleanCache() {} 54 | #endif 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLMemoryStats/WebGLMemoryStats.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Kongregate { 5 | public class WebGLMemoryStats : MonoBehaviour { 6 | [Tooltip("Interval (in seconds) between log entries")] 7 | public uint LogIntervalSeconds = 15; 8 | 9 | public static uint GetUsedMemorySize() { 10 | return GetTotalStackSize() + GetStaticMemorySize() + GetDynamicMemorySize(); 11 | } 12 | 13 | #if UNITY_WEBGL && !UNITY_EDITOR 14 | public static uint GetFreeMemorySize() { 15 | return GetTotalMemorySize() - GetUsedMemorySize(); 16 | } 17 | 18 | void Start() { 19 | InvokeRepeating("Log", 0, LogIntervalSeconds); 20 | } 21 | 22 | private void Log() { 23 | var total = GetTotalMemorySize() / 1024 / 1024; 24 | var used = GetUsedMemorySize() / 1024 / 1024; 25 | var free = GetFreeMemorySize() / 1024 / 1024; 26 | Debug.Log(string.Format("WebGL Memory - Total: {0}MB, Used: {1}MB, Free: {2}MB", total, used, free)); 27 | } 28 | 29 | [DllImport("__Internal")] 30 | public static extern uint GetTotalMemorySize(); 31 | 32 | [DllImport("__Internal")] 33 | public static extern uint GetTotalStackSize(); 34 | 35 | [DllImport("__Internal")] 36 | public static extern uint GetStaticMemorySize(); 37 | 38 | [DllImport("__Internal")] 39 | public static extern uint GetDynamicMemorySize(); 40 | #else 41 | public static uint GetFreeMemorySize() { return 0; } 42 | public static uint GetTotalMemorySize() { return 0; } 43 | public static uint GetTotalStackSize() { return 0; } 44 | public static uint GetStaticMemorySize() { return 0; } 45 | public static uint GetDynamicMemorySize() { return 0; } 46 | #endif 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-WebGL-Utilities 2 | Some helpful utilities for Unity WebGL games based on and inspired by the Unity team's blog posts and WebGL essentials asset packages. 3 | 4 | ## Installing 5 | Grab the [latest release](https://github.com/kongregate/Unity-WebGL-Utilities/releases) and import the `unitypackage` into your project. 6 | 7 | ## WebGLMemoryStats 8 | 9 | This is a simple behavior that you can add to a persistent game object. It will periodically log WebGL memory statistics to the browser console to help you tune your WebGL memory size: 10 | 11 | ![](http://kong.dreamhosters.com/grabs/Default_unity_-_mtx-unity_-_WebGL__Personal___OpenGL_4_1__1E96A487.png) 12 | 13 | ![](http://kong.dreamhosters.com/grabs/Play_webgl-test__a_free_online_game_on_Kongregate_1E96A97A.png) 14 | 15 | ## Updated CachedXMLHttpRequest 16 | 17 | The original version of CachedXMLHttpRequest unfortunately has a few bugs. This package includes an updated drop-in replacement that resolves the following issues: 18 | 19 | * An error dialog is displayed in Firefox private browsing mode 20 | * When used with Safari and content in an iframe the plugin is non-functional 21 | * Synchronous XHR requests are used to revalidate resources 22 | 23 | The updated version also adds some functionality to give you finer control over the XHR cache: 24 | 25 | * `Module.CachedXMLHttpRequestBlacklist` can be set to an array of `RegExp` or string objects to disable caching for matching URLs (useful to prevent caching of API endpoints, etc) 26 | * `Module.CachedXMLHttpRequestRevalidateBlacklist` can be set to an array of `RegExp` or string objects to disable re-validation for matching URLs (helpful if you use explicit versioning on your asset bundles) 27 | 28 | For example, the following configuration will never cache files containing `.xml`, `.php`, or `xhr_nocache`, and requests with `.unity3d` in them will not be re-validated when being loaded: 29 | 30 | ```js 31 | var Module = { 32 | TOTAL_MEMORY: 268435456, 33 | CachedXMLHttpRequestCacheBlacklist: [/\.xml/, /\.php/, 'xhr_nocache'], 34 | CachedXMLHttpRequestRevalidateBlacklist: [/\.unity3d/] 35 | }; 36 | ``` 37 | 38 | ## CachedXHRExtensions 39 | 40 | This package also provides extensions to the `CachedXMLHttpRequest` addon which allow you to clear and query for the existence of items in the cache. This can be useful if you pre-fetch your asset bundles on startup to avoid doing so multiple times. The classes used below are in the `Kongregate` namespace. 41 | 42 | **Clear the cache:** 43 | ```csharp 44 | CachedXHRExtensions.CleanCache(); 45 | ``` 46 | 47 | **Query the cache:** 48 | ```csharp 49 | IEnumerator CheckIfAssetExists() { 50 | var query = new CacheEntryQuery("https://whatever.io/file.xml"); 51 | yield return query; 52 | if (query.IsCached) { 53 | Debug.Log("Asset exists in cache!"); 54 | } 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXMLHttpRequest/readme.txt: -------------------------------------------------------------------------------- 1 | CachedXMLHttpRequest implements automatic caching of WWW or WebRequest responses in the indexedDB in Unity WebGL. It has been designed for caching downloaded asset bundles and supports response caching for GET requests without custom headers or request body. CachedXMLHttpRequest can be used as a more memory efficient solution for caching asset bundles in comparison with WWW.LoadFromCacheOrDownload. 2 | 3 | The difference between caching asset bundles with LoadFromCacheOrDownload and CachedXMLHttpRequest is the following: 4 | 5 | All the files previously cached with LoadFromCacheOrDownload are loaded from the indexedDB into the memory file system on application startup. This might include cached files that will be used at some point in the future or might not be used at all, therefore wasting the main memory. LoadFromCacheOrDownload requires a version number provided by the application. 6 | 7 | When downloading asset bundles with WWW or WebRequest while CachedXMLHttpRequest is enabled, only the currently requested file will be copied from the indexedDB into the main memory (or downloaded), after the file content is transferred to the application, this memory will get garbage collected, therefore no main memory is wasted. CachedXMLHttpRequest file versioning is based on the Last-Modified and ETag headers, provided by the server. 8 | 9 | In addition, starting from Unity 5.4, CachedXMLHttpRequest can be used to cache the initially loaded .js, .data and .mem files in the indexedDB (it may serve as replacement for the "Data caching" build option which caches all the files in the build). 10 | 11 | 12 | CachedXMLHttpRequest can be configured using the following Module variables: 13 | 14 | Set Module.CachedXMLHttpRequestDisable to true in order to fully disable indexedDB caching. 15 | Set Module.CachedXMLHttpRequestSilent to true in order to disable cache logs in the console. 16 | Set Module.CachedXMLHttpRequestLoader to true in order to enable caching of the initially loaded .js, .data and .mem files (applies to Unity 5.4 and above) 17 | Set Module.CachedXMLHttpRequestBlacklist to an array of RegExp or string objects to disable caching for matching URLs (useful to prevent caching of API endpoints, etc) 18 | Set Module.CachedXMLHttpRequestRevalidateBlacklist to an array of RegExp or string objects to disable re-validation for matching URLs (helpful if you use explicit versioning on your asset bundles) 19 | 20 | Module variables can be initialized directly in the index.html in the following way (note that initialization is optional): 21 | 22 | var Module = { 23 | TOTAL_MEMORY: 268435456, 24 | CachedXMLHttpRequestLoader: true, 25 | CachedXMLHttpRequestCacheBlacklist: [/\.xml/, /\.php/, 'xhr_nocache'], 26 | CachedXMLHttpRequestRevalidateBlacklist: [/\.unity3d/] 27 | ... 28 | }; 29 | -------------------------------------------------------------------------------- /Assets/Plugins/WebGLCachedXMLHttpRequest/WebGLCachedXMLHttpRequest.jspre: -------------------------------------------------------------------------------- 1 | function CachedXMLHttpRequest() { 2 | var self = this, xhr = new CachedXMLHttpRequest.XMLHttpRequest(), cache = {}; 3 | 4 | function send() { 5 | var onload = xhr.onload; 6 | xhr.onload = function (e) { 7 | var meta = { 8 | requestURL: cache.requestURL, 9 | responseURL: xhr.responseURL, 10 | responseType: xhr.responseType, 11 | lastModified: xhr.getResponseHeader("Last-Modified"), 12 | eTag: xhr.getResponseHeader("ETag"), 13 | }; 14 | if (xhr.status == 200 && (meta.lastModified || meta.eTag)) { 15 | meta.size = xhr.response.byteLength; 16 | CachedXMLHttpRequest.cache.put(cache.requestURL, meta, xhr.response, function (err) { 17 | CachedXMLHttpRequest.log("'" + cache.requestURL + "' downloaded successfully (" + xhr.response.byteLength + " bytes) " + 18 | (err ? "but not stored in indexedDB cache due to error: " + err.message : "and stored in indexedDB cache.")); 19 | if (onload) 20 | onload(e); 21 | }); 22 | } else { 23 | if (xhr.status == 304) { 24 | cache.override = true; 25 | CachedXMLHttpRequest.log("'" + cache.requestURL + "' served from indexedDB cache (" + cache.response.byteLength + " bytes)."); 26 | } 27 | if (onload) 28 | onload(e); 29 | } 30 | }; 31 | return xhr.send.apply(xhr, arguments); 32 | } 33 | 34 | function loadComplete() { 35 | CachedXMLHttpRequest.log("'" + cache.requestURL + "' served from indexedDB cache (" + cache.response.byteLength + " bytes)."); 36 | if (xhr.onload) 37 | xhr.onload(); 38 | } 39 | 40 | function revalidateCrossOriginRequest(meta, self, sendArguments) { 41 | var headXHR = new CachedXMLHttpRequest.XMLHttpRequest(); 42 | var onerror = xhr.onerror; 43 | headXHR.open("HEAD", meta.requestURL, cache.async); 44 | headXHR.onload = function() { 45 | cache.override = meta.lastModified ? meta.lastModified == headXHR.getResponseHeader("Last-Modified") : meta.eTag && meta.eTag == getETag(headXHR); 46 | if (!cache.override) 47 | return send.apply(self, sendArguments); 48 | loadComplete(); 49 | }; 50 | headXHR.onerror = function(e) { 51 | // if there is an error with the head request forward the request back 52 | // to the request that unity knows about. 53 | if (onerror) onerror(e); 54 | } 55 | headXHR.send(); 56 | } 57 | 58 | Object.defineProperty(self, "open", { value: function (method, url, async) { 59 | cache = { method: method, requestURL: CachedXMLHttpRequest.cache.requestURL(url), async: async }; 60 | return xhr.open.apply(xhr, arguments); 61 | }}); 62 | 63 | Object.defineProperty(self, "setRequestHeader", { value: function () { 64 | cache.customHeaders = true; 65 | return xhr.setRequestHeader.apply(xhr, arguments); 66 | }}); 67 | 68 | Object.defineProperty(self, "send", { value: function (data) { 69 | var sendArguments = arguments; 70 | var absoluteUrlMatch = cache.requestURL.match("^https?:\/\/[^\/]+\/"); 71 | if (!absoluteUrlMatch || cache.customHeaders || data || cache.method != "GET" || !cache.async || xhr.responseType != "arraybuffer") 72 | return xhr.send.apply(xhr, sendArguments); 73 | CachedXMLHttpRequest.cache.get(cache.requestURL, function (err, result) { 74 | if (err || !result || !result.meta || result.meta.responseType != xhr.responseType) 75 | return send.apply(self, sendArguments); 76 | cache.status = 200; 77 | cache.statusText = "OK"; 78 | cache.response = result.response; 79 | cache.responseURL = result.meta.responseURL; 80 | 81 | if (CachedXMLHttpRequest.checkBlacklist(Module.CachedXMLHttpRequestRevalidateBlacklist, cache.requestURL)) { 82 | cache.override = true; 83 | return loadComplete(); 84 | } 85 | 86 | if (window.location.href.lastIndexOf(absoluteUrlMatch[0], 0)) 87 | return revalidateCrossOriginRequest(result.meta, self, sendArguments); 88 | if (result.meta.lastModified) 89 | xhr.setRequestHeader("If-Modified-Since", result.meta.lastModified); 90 | else if (result.meta.eTag) 91 | xhr.setRequestHeader("If-None-Match", result.meta.eTag); 92 | xhr.setRequestHeader("Cache-Control", "no-cache"); 93 | return send.apply(self, sendArguments); 94 | }); 95 | }}); 96 | 97 | ["abort", "getAllResponseHeaders", "getResponseHeader", "overrideMimeType", "addEventListener"].forEach(function (method) { 98 | Object.defineProperty(self, method, { value: function () { return xhr[method].apply(xhr, arguments); } }); 99 | }); 100 | 101 | ["readyState", "response", "responseText", "responseType", "responseURL", "responseXML", "status", "statusText", "timeout", "upload", "withCredentials", 102 | "onloadstart", "onprogress", "onabort", "onerror", "onload", "ontimeout", "onloadend", "onreadystatechange"].forEach(function (property) { 103 | Object.defineProperty(self, property, { 104 | get: function () { return (cache.override && cache[property]) ? cache[property] : xhr[property]; }, 105 | set: function (value) { xhr[property] = value; }, 106 | }); 107 | }); 108 | 109 | } 110 | 111 | CachedXMLHttpRequest.XMLHttpRequest = window.XMLHttpRequest; 112 | 113 | CachedXMLHttpRequest.log = function (message) { 114 | if (Module.CachedXMLHttpRequestSilent !== true) 115 | console.log("[CachedXMLHttpRequest] " + message); 116 | }; 117 | 118 | CachedXMLHttpRequest.checkBlacklist = function(list, url) { 119 | list = list || []; 120 | list = Array.isArray(list) ? list : [list]; 121 | for (var i = 0; i < list.length; i++) { 122 | var regexp = list[i]; 123 | if (typeof regexp === "string") regexp = new RegExp(regexp); 124 | if (regexp instanceof RegExp && regexp.test(url)) return true; 125 | } 126 | return false; 127 | }; 128 | 129 | CachedXMLHttpRequest.cache = { 130 | database: "CachedXMLHttpRequest", 131 | version: 1, 132 | store: "cache", 133 | indexedDB: window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB, 134 | link: document.createElement("a"), 135 | requestURL: function (url) { 136 | this.link.href = url; 137 | return this.link.href; 138 | 139 | }, 140 | id: function (requestURL) { 141 | return encodeURIComponent(requestURL); 142 | 143 | }, 144 | queue: [], 145 | processQueue: function () { 146 | var self = this; 147 | self.queue.forEach(function (queued) { self[queued.action].apply(self, queued.arguments); }); 148 | self.queue = []; 149 | 150 | }, 151 | init: function () { 152 | var self = this, onError = function(e) { 153 | CachedXMLHttpRequest.log("can not open indexedDB database: " + e.message); 154 | self.indexedDB = null; 155 | self.processQueue(); 156 | if (e.preventDefault) e.preventDefault(); 157 | }; 158 | if (!self.indexedDB) 159 | return CachedXMLHttpRequest.log("indexedDB is not available"); 160 | var openDB; 161 | try { 162 | openDB = indexedDB.open(self.database, self.version); 163 | } catch(e) { 164 | return onError(new Error("indexedDB access denied")); 165 | } 166 | openDB.onupgradeneeded = function (e) { 167 | var db = e.target.result; 168 | var transaction = e.target.transaction; 169 | var objectStore; 170 | if (db.objectStoreNames.contains(self.store)) { 171 | objectStore = transaction.objectStore(self.store); 172 | } else { 173 | objectStore = db.createObjectStore(self.store, {keyPath: "id"}); 174 | objectStore.createIndex("meta", "meta", {unique: false}); 175 | } 176 | objectStore.clear(); 177 | }; 178 | openDB.onerror = onError; 179 | openDB.onsuccess = function (e) { 180 | self.db = e.target.result; 181 | self.processQueue(); 182 | }; 183 | 184 | }, 185 | put: function (requestURL, meta, response, callback) { 186 | if (CachedXMLHttpRequest.checkBlacklist(Module.CachedXMLHttpRequestBlacklist, requestURL)) 187 | return callback(new Error("requestURL was on the cache blacklist")); 188 | 189 | var self = this; 190 | if (!self.indexedDB) 191 | return callback(new Error("indexedDB is not available")); 192 | if (!self.db) 193 | return self.queue.push({action: "put", arguments: arguments}); 194 | meta.version = self.version; 195 | var putDB = self.db.transaction([self.store], "readwrite").objectStore(self.store).put({id: self.id(requestURL), meta: meta, response: response}); 196 | putDB.onerror = function (e) { e.preventDefault(); callback(new Error("failed to put request into indexedDB cache")); }; 197 | putDB.onsuccess = function () { callback(null); }; 198 | 199 | }, 200 | get: function (requestURL, callback) { 201 | if (CachedXMLHttpRequest.checkBlacklist(Module.CachedXMLHttpRequestBlacklist, requestURL)) 202 | return callback(new Error("requestURL was on the cache blacklist")); 203 | 204 | var self = this; 205 | if (!self.indexedDB) 206 | return callback(new Error("indexedDB is not available")); 207 | if (!self.db) 208 | return self.queue.push({action: "get", arguments: arguments}); 209 | var getDB = self.db.transaction([self.store], "readonly").objectStore(self.store).get(self.id(requestURL)); 210 | getDB.onerror = function (e) { e.preventDefault(); callback(new Error("failed to get request from indexedDB cache")); }; 211 | getDB.onsuccess = function (e) { callback(null, e.target.result); }; 212 | } 213 | }; 214 | 215 | CachedXMLHttpRequest.cache.init(); 216 | 217 | CachedXMLHttpRequest.wrap = function (func) { 218 | return function () { 219 | var realXMLHttpRequest = XMLHttpRequest, result; 220 | window.XMLHttpRequest = CachedXMLHttpRequest; 221 | try { 222 | result = func.apply(this, arguments); 223 | } catch (e) { 224 | window.XMLHttpRequest = realXMLHttpRequest; 225 | throw e; 226 | } 227 | window.XMLHttpRequest = realXMLHttpRequest; 228 | return result; 229 | }; 230 | }; 231 | 232 | if (Module.CachedXMLHttpRequestDisable !== true) { 233 | if (Module.CachedXMLHttpRequestLoader === true) { 234 | if (typeof LoadCompressedFile == "function") 235 | LoadCompressedFile = CachedXMLHttpRequest.wrap(LoadCompressedFile); 236 | if (typeof DecompressAndLoadFile == "function") 237 | DecompressAndLoadFile = CachedXMLHttpRequest.wrap(DecompressAndLoadFile); 238 | } 239 | Object.defineProperty(Module, "asmLibraryArg", { 240 | get: function () { return Module.realAsmLibraryArg; }, 241 | set: function (value) { 242 | if (typeof value == "object" && typeof value._JS_WebRequest_Create == "function") 243 | value._JS_WebRequest_Create = CachedXMLHttpRequest.wrap(value._JS_WebRequest_Create); 244 | Module.realAsmLibraryArg = value; 245 | }, 246 | }); 247 | } 248 | --------------------------------------------------------------------------------