Skip to content

Commit 62eee37

Browse files
committed
📦 Add the stock version of CachedXMLHttpRequest
1 parent 7b85706 commit 62eee37

5 files changed

Lines changed: 269 additions & 0 deletions

File tree

Assets/Plugins/WebGLCachedXMLHttpRequest.meta

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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." : "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 revalidateCrossOriginRequest(meta, self, sendArguments) {
35+
var headXHR = new CachedXMLHttpRequest.XMLHttpRequest();
36+
headXHR.open("HEAD", meta.requestURL, false);
37+
headXHR.send();
38+
cache.override = meta.lastModified ? meta.lastModified == headXHR.getResponseHeader("Last-Modified") : meta.eTag && meta.eTag == headXHR.getResponseHeader("ETag");
39+
if (!cache.override)
40+
return send.apply(self, sendArguments);
41+
CachedXMLHttpRequest.log("'" + cache.requestURL + "' served from indexedDB cache (" + cache.response.byteLength + " bytes).");
42+
if (xhr.onload)
43+
xhr.onload();
44+
}
45+
46+
Object.defineProperty(self, "open", { value: function (method, url, async) {
47+
cache = { method: method, requestURL: CachedXMLHttpRequest.cache.requestURL(url), async: async };
48+
return xhr.open.apply(xhr, arguments);
49+
}});
50+
51+
Object.defineProperty(self, "setRequestHeader", { value: function () {
52+
cache.customHeaders = true;
53+
return xhr.setRequestHeader.apply(xhr, arguments);
54+
}});
55+
56+
Object.defineProperty(self, "send", { value: function (data) {
57+
var sendArguments = arguments;
58+
var absoluteUrlMatch = cache.requestURL.match("^https?:\/\/[^\/]+\/");
59+
if (!absoluteUrlMatch || cache.customHeaders || data || cache.method != "GET" || !cache.async || xhr.responseType != "arraybuffer")
60+
return xhr.send.apply(xhr, sendArguments);
61+
CachedXMLHttpRequest.cache.get(cache.requestURL, function (err, result) {
62+
if (err || !result || !result.meta || result.meta.responseType != xhr.responseType)
63+
return send.apply(self, sendArguments);
64+
cache.status = 200;
65+
cache.statusText = "OK";
66+
cache.response = result.response;
67+
cache.responseURL = result.meta.responseURL;
68+
if (window.location.href.lastIndexOf(absoluteUrlMatch[0], 0))
69+
return revalidateCrossOriginRequest(result.meta, self, sendArguments);
70+
if (result.meta.lastModified)
71+
xhr.setRequestHeader("If-Modified-Since", result.meta.lastModified);
72+
else if (result.meta.eTag)
73+
xhr.setRequestHeader("If-None-Match", result.meta.eTag);
74+
xhr.setRequestHeader("Cache-Control", "no-cache");
75+
return send.apply(self, sendArguments);
76+
});
77+
}});
78+
79+
["abort", "getAllResponseHeaders", "getResponseHeader", "overrideMimeType", "addEventListener"].forEach(function (method) {
80+
Object.defineProperty(self, method, { value: function () { return xhr[method].apply(xhr, arguments); } });
81+
});
82+
83+
["readyState", "response", "responseText", "responseType", "responseURL", "responseXML", "status", "statusText", "timeout", "upload", "withCredentials",
84+
"onloadstart", "onprogress", "onabort", "onerror", "onload", "ontimeout", "onloadend", "onreadystatechange"].forEach(function (property) {
85+
Object.defineProperty(self, property, {
86+
get: function () { return (cache.override && cache[property]) ? cache[property] : xhr[property]; },
87+
set: function (value) { xhr[property] = value; },
88+
});
89+
});
90+
91+
}
92+
93+
CachedXMLHttpRequest.XMLHttpRequest = window.XMLHttpRequest;
94+
95+
CachedXMLHttpRequest.log = function (message) {
96+
if (Module.CachedXMLHttpRequestSilent !== true)
97+
console.log("[CachedXMLHttpRequest] " + message);
98+
};
99+
100+
CachedXMLHttpRequest.cache = {
101+
database: "CachedXMLHttpRequest",
102+
version: 1,
103+
store: "cache",
104+
indexedDB: window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB,
105+
link: document.createElement("a"),
106+
requestURL: function (url) {
107+
this.link.href = url;
108+
return this.link.href;
109+
110+
},
111+
id: function (requestURL) {
112+
return encodeURIComponent(requestURL);
113+
114+
},
115+
queue: [],
116+
processQueue: function () {
117+
var self = this;
118+
self.queue.forEach(function (queued) { self[queued.action].apply(self, queued.arguments) });
119+
self.queue = [];
120+
121+
},
122+
init: function () {
123+
var self = this;
124+
if (!self.indexedDB)
125+
return CachedXMLHttpRequest.log("indexedDB is not available");
126+
var openDB;
127+
try {
128+
openDB = indexedDB.open(self.database, self.version);
129+
} catch(e) {
130+
return CachedXMLHttpRequest.log("indexedDB access denied");
131+
}
132+
openDB.onupgradeneeded = function (e) {
133+
var db = e.target.result;
134+
var transaction = e.target.transaction;
135+
var objectStore;
136+
if (db.objectStoreNames.contains(self.store)) {
137+
objectStore = transaction.objectStore(self.store);
138+
} else {
139+
objectStore = db.createObjectStore(self.store, {keyPath: "id"});
140+
objectStore.createIndex("meta", "meta", {unique: false});
141+
}
142+
objectStore.clear();
143+
}
144+
openDB.onerror = function (e) {
145+
CachedXMLHttpRequest.log("can not open indexedDB database");
146+
self.indexedDB = null;
147+
self.processQueue();
148+
}
149+
openDB.onsuccess = function (e) {
150+
self.db = e.target.result;
151+
self.processQueue();
152+
}
153+
154+
},
155+
put: function (requestURL, meta, response, callback) {
156+
var self = this;
157+
if (!self.indexedDB)
158+
return callback(new Error("indexedDB is not available"));
159+
if (!self.db)
160+
return self.queue.push({action: "put", arguments: arguments});
161+
meta.version = self.version;
162+
var putDB = self.db.transaction([self.store], "readwrite").objectStore(self.store).put({id: self.id(requestURL), meta: meta, response: response});
163+
putDB.onerror = function (e) { callback(new Error("failed to put request into indexedDB cache")); }
164+
putDB.onsuccess = function (e) { callback(null); }
165+
166+
},
167+
get: function (requestURL, callback) {
168+
var self = this;
169+
if (!self.indexedDB)
170+
return callback(new Error("indexedDB is not available"));
171+
if (!self.db)
172+
return self.queue.push({action: "get", arguments: arguments});
173+
var getDB = self.db.transaction([self.store], "readonly").objectStore(self.store).get(self.id(requestURL));
174+
getDB.onerror = function (e) { callback(new Error("failed to get request from indexedDB cache")); }
175+
getDB.onsuccess = function (e) { callback(null, e.target.result); }
176+
177+
},
178+
};
179+
180+
CachedXMLHttpRequest.cache.init();
181+
182+
CachedXMLHttpRequest.wrap = function (func) {
183+
return function () {
184+
var realXMLHttpRequest = XMLHttpRequest;
185+
XMLHttpRequest = CachedXMLHttpRequest;
186+
try {
187+
var result = func.apply(this, arguments);
188+
} catch (e) {
189+
XMLHttpRequest = realXMLHttpRequest;
190+
throw e;
191+
}
192+
XMLHttpRequest = realXMLHttpRequest;
193+
return result;
194+
};
195+
};
196+
197+
if (Module.CachedXMLHttpRequestDisable !== true) {
198+
if (Module.CachedXMLHttpRequestLoader === true) {
199+
if (typeof LoadCompressedFile == "function")
200+
LoadCompressedFile = CachedXMLHttpRequest.wrap(LoadCompressedFile);
201+
if (typeof DecompressAndLoadFile == "function")
202+
DecompressAndLoadFile = CachedXMLHttpRequest.wrap(DecompressAndLoadFile);
203+
}
204+
Object.defineProperty(Module, "asmLibraryArg", {
205+
get: function () { return Module.realAsmLibraryArg; },
206+
set: function (value) {
207+
if (typeof value == "object" && typeof value._JS_WebRequest_Create == "function")
208+
value._JS_WebRequest_Create = CachedXMLHttpRequest.wrap(value._JS_WebRequest_Create);
209+
Module.realAsmLibraryArg = value;
210+
},
211+
});
212+
}

Assets/Plugins/WebGLCachedXMLHttpRequest/WebGLCachedXMLHttpRequest.jspre.meta

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 previousely 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+
18+
Module variables can be initialized directly in the index.html in the following way (note that initialization is optional):
19+
20+
var Module = {
21+
TOTAL_MEMORY: 268435456,
22+
CachedXMLHttpRequestLoader: true,
23+
...
24+
};

Assets/Plugins/WebGLCachedXMLHttpRequest/readme.txt.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)