diff --git a/Resources/ReactUnity/polyfills/abortcontroller.js b/Resources/ReactUnity/polyfills/abortcontroller.js
new file mode 100644
index 00000000..98c05fae
--- /dev/null
+++ b/Resources/ReactUnity/polyfills/abortcontroller.js
@@ -0,0 +1,385 @@
+https://cdn.jsdelivr.net/npm/abortcontroller-polyfill@1.7.5/dist/abortcontroller-polyfill-only.js
+(function (factory) {
+ typeof define === 'function' && define.amd ? define(factory) :
+ factory();
+})((function () { 'use strict';
+
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ Object.defineProperty(Constructor, "prototype", {
+ writable: false
+ });
+ return Constructor;
+ }
+
+ function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ writable: true,
+ configurable: true
+ }
+ });
+ Object.defineProperty(subClass, "prototype", {
+ writable: false
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+ }
+
+ function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+ }
+
+ function _setPrototypeOf(o, p) {
+ _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+ }
+
+ function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+
+ try {
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return self;
+ }
+
+ function _possibleConstructorReturn(self, call) {
+ if (call && (typeof call === "object" || typeof call === "function")) {
+ return call;
+ } else if (call !== void 0) {
+ throw new TypeError("Derived constructors may only return object or undefined");
+ }
+
+ return _assertThisInitialized(self);
+ }
+
+ function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+
+ return _possibleConstructorReturn(this, result);
+ };
+ }
+
+ function _superPropBase(object, property) {
+ while (!Object.prototype.hasOwnProperty.call(object, property)) {
+ object = _getPrototypeOf(object);
+ if (object === null) break;
+ }
+
+ return object;
+ }
+
+ function _get() {
+ if (typeof Reflect !== "undefined" && Reflect.get) {
+ _get = Reflect.get.bind();
+ } else {
+ _get = function _get(target, property, receiver) {
+ var base = _superPropBase(target, property);
+
+ if (!base) return;
+ var desc = Object.getOwnPropertyDescriptor(base, property);
+
+ if (desc.get) {
+ return desc.get.call(arguments.length < 3 ? target : receiver);
+ }
+
+ return desc.value;
+ };
+ }
+
+ return _get.apply(this, arguments);
+ }
+
+ var Emitter = /*#__PURE__*/function () {
+ function Emitter() {
+ _classCallCheck(this, Emitter);
+
+ Object.defineProperty(this, 'listeners', {
+ value: {},
+ writable: true,
+ configurable: true
+ });
+ }
+
+ _createClass(Emitter, [{
+ key: "addEventListener",
+ value: function addEventListener(type, callback, options) {
+ if (!(type in this.listeners)) {
+ this.listeners[type] = [];
+ }
+
+ this.listeners[type].push({
+ callback: callback,
+ options: options
+ });
+ }
+ }, {
+ key: "removeEventListener",
+ value: function removeEventListener(type, callback) {
+ if (!(type in this.listeners)) {
+ return;
+ }
+
+ var stack = this.listeners[type];
+
+ for (var i = 0, l = stack.length; i < l; i++) {
+ if (stack[i].callback === callback) {
+ stack.splice(i, 1);
+ return;
+ }
+ }
+ }
+ }, {
+ key: "dispatchEvent",
+ value: function dispatchEvent(event) {
+ if (!(event.type in this.listeners)) {
+ return;
+ }
+
+ var stack = this.listeners[event.type];
+ var stackToCall = stack.slice();
+
+ for (var i = 0, l = stackToCall.length; i < l; i++) {
+ var listener = stackToCall[i];
+
+ try {
+ listener.callback.call(this, event);
+ } catch (e) {
+ Promise.resolve().then(function () {
+ throw e;
+ });
+ }
+
+ if (listener.options && listener.options.once) {
+ this.removeEventListener(event.type, listener.callback);
+ }
+ }
+
+ return !event.defaultPrevented;
+ }
+ }]);
+
+ return Emitter;
+ }();
+
+ var AbortSignal = /*#__PURE__*/function (_Emitter) {
+ _inherits(AbortSignal, _Emitter);
+
+ var _super = _createSuper(AbortSignal);
+
+ function AbortSignal() {
+ var _this;
+
+ _classCallCheck(this, AbortSignal);
+
+ _this = _super.call(this); // Some versions of babel does not transpile super() correctly for IE <= 10, if the parent
+ // constructor has failed to run, then "this.listeners" will still be undefined and then we call
+ // the parent constructor directly instead as a workaround. For general details, see babel bug:
+ // https://github.com/babel/babel/issues/3041
+ // This hack was added as a fix for the issue described here:
+ // https://github.com/Financial-Times/polyfill-library/pull/59#issuecomment-477558042
+
+ if (!_this.listeners) {
+ Emitter.call(_assertThisInitialized(_this));
+ } // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
+ // we want Object.keys(new AbortController().signal) to be [] for compat with the native impl
+
+
+ Object.defineProperty(_assertThisInitialized(_this), 'aborted', {
+ value: false,
+ writable: true,
+ configurable: true
+ });
+ Object.defineProperty(_assertThisInitialized(_this), 'onabort', {
+ value: null,
+ writable: true,
+ configurable: true
+ });
+ Object.defineProperty(_assertThisInitialized(_this), 'reason', {
+ value: undefined,
+ writable: true,
+ configurable: true
+ });
+ return _this;
+ }
+
+ _createClass(AbortSignal, [{
+ key: "toString",
+ value: function toString() {
+ return '[object AbortSignal]';
+ }
+ }, {
+ key: "dispatchEvent",
+ value: function dispatchEvent(event) {
+ if (event.type === 'abort') {
+ this.aborted = true;
+
+ if (typeof this.onabort === 'function') {
+ this.onabort.call(this, event);
+ }
+ }
+
+ _get(_getPrototypeOf(AbortSignal.prototype), "dispatchEvent", this).call(this, event);
+ }
+ }]);
+
+ return AbortSignal;
+ }(Emitter);
+ var AbortController = /*#__PURE__*/function () {
+ function AbortController() {
+ _classCallCheck(this, AbortController);
+
+ // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
+ // we want Object.keys(new AbortController()) to be [] for compat with the native impl
+ Object.defineProperty(this, 'signal', {
+ value: new AbortSignal(),
+ writable: true,
+ configurable: true
+ });
+ }
+
+ _createClass(AbortController, [{
+ key: "abort",
+ value: function abort(reason) {
+ var event;
+
+ try {
+ event = new Event('abort');
+ } catch (e) {
+ if (typeof document !== 'undefined') {
+ if (!document.createEvent) {
+ // For Internet Explorer 8:
+ event = document.createEventObject();
+ event.type = 'abort';
+ } else {
+ // For Internet Explorer 11:
+ event = document.createEvent('Event');
+ event.initEvent('abort', false, false);
+ }
+ } else {
+ // Fallback where document isn't available:
+ event = {
+ type: 'abort',
+ bubbles: false,
+ cancelable: false
+ };
+ }
+ }
+
+ var signalReason = reason;
+
+ if (signalReason === undefined) {
+ if (typeof document === 'undefined') {
+ signalReason = new Error('This operation was aborted');
+ signalReason.name = 'AbortError';
+ } else {
+ try {
+ signalReason = new DOMException('signal is aborted without reason');
+ } catch (err) {
+ // IE 11 does not support calling the DOMException constructor, use a
+ // regular error object on it instead.
+ signalReason = new Error('This operation was aborted');
+ signalReason.name = 'AbortError';
+ }
+ }
+ }
+
+ this.signal.reason = signalReason;
+ this.signal.dispatchEvent(event);
+ }
+ }, {
+ key: "toString",
+ value: function toString() {
+ return '[object AbortController]';
+ }
+ }]);
+
+ return AbortController;
+ }();
+
+ if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+ // These are necessary to make sure that we get correct output for:
+ // Object.prototype.toString.call(new AbortController())
+ AbortController.prototype[Symbol.toStringTag] = 'AbortController';
+ AbortSignal.prototype[Symbol.toStringTag] = 'AbortSignal';
+ }
+
+ function polyfillNeeded(self) {
+ if (self.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
+ console.log('__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL=true is set, will force install polyfill');
+ return true;
+ } // Note that the "unfetch" minimal fetch polyfill defines fetch() without
+ // defining window.Request, and this polyfill need to work on top of unfetch
+ // so the below feature detection needs the !self.AbortController part.
+ // The Request.prototype check is also needed because Safari versions 11.1.2
+ // up to and including 12.1.x has a window.AbortController present but still
+ // does NOT correctly implement abortable fetch:
+ // https://bugs.webkit.org/show_bug.cgi?id=174980#c2
+
+
+ return typeof self.Request === 'function' && !self.Request.prototype.hasOwnProperty('signal') || !self.AbortController;
+ }
+
+ (function (self) {
+
+ if (!polyfillNeeded(self)) {
+ return;
+ }
+
+ self.AbortController = AbortController;
+ self.AbortSignal = AbortSignal;
+ })(typeof self !== 'undefined' ? self : global);
+
+}));
diff --git a/Resources/ReactUnity/polyfills/abortcontroller.js.meta b/Resources/ReactUnity/polyfills/abortcontroller.js.meta
new file mode 100644
index 00000000..9eddf32a
--- /dev/null
+++ b/Resources/ReactUnity/polyfills/abortcontroller.js.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: b14017f71eca05b4e8b3e8722ee528ee
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 11500000, guid: a6814ccfd69dca246adc5eab902a45db, type: 3}
diff --git a/Resources/ReactUnity/polyfills/fetch.js b/Resources/ReactUnity/polyfills/fetch.js
index d43f99d3..90415720 100644
--- a/Resources/ReactUnity/polyfills/fetch.js
+++ b/Resources/ReactUnity/polyfills/fetch.js
@@ -1,20 +1,24 @@
+// https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.20/dist/fetch.umd.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.WHATWGFetch = {})));
}(this, (function (exports) { 'use strict';
- var global =
+ /* eslint-disable no-prototype-builtins */
+ var g =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof self !== 'undefined' && self) ||
- (typeof global !== 'undefined' && global);
+ // eslint-disable-next-line no-undef
+ (typeof global !== 'undefined' && global) ||
+ {};
var support = {
- searchParams: 'URLSearchParams' in global,
- iterable: 'Symbol' in global && 'iterator' in Symbol,
+ searchParams: 'URLSearchParams' in g,
+ iterable: 'Symbol' in g && 'iterator' in Symbol,
blob:
- 'FileReader' in global &&
- 'Blob' in global &&
+ 'FileReader' in g &&
+ 'Blob' in g &&
(function() {
try {
new Blob();
@@ -23,8 +27,8 @@
return false
}
})(),
- formData: 'FormData' in global,
- arrayBuffer: 'ArrayBuffer' in global
+ formData: 'FormData' in g,
+ arrayBuffer: 'ArrayBuffer' in g
};
function isDataView(obj) {
@@ -56,7 +60,7 @@
name = String(name);
}
if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
- throw new TypeError('Invalid character in header field name')
+ throw new TypeError('Invalid character in header field name: "' + name + '"')
}
return name.toLowerCase()
}
@@ -95,6 +99,9 @@
}, this);
} else if (Array.isArray(headers)) {
headers.forEach(function(header) {
+ if (header.length != 2) {
+ throw new TypeError('Headers constructor: expected name/value pair to be length 2, found' + header.length)
+ }
this.append(header[0], header[1]);
}, this);
} else if (headers) {
@@ -165,6 +172,7 @@
}
function consumed(body) {
+ if (body._noBody) return
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'))
}
@@ -192,7 +200,9 @@
function readBlobAsText(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
- reader.readAsText(blob);
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
+ var encoding = match ? match[1] : 'utf-8';
+ reader.readAsText(blob, encoding);
return promise
}
@@ -230,9 +240,11 @@
semantic of setting Request.bodyUsed in the constructor before
_initBody is called.
*/
+ // eslint-disable-next-line no-self-assign
this.bodyUsed = this.bodyUsed;
this._bodyInit = body;
if (!body) {
+ this._noBody = true;
this._bodyText = '';
} else if (typeof body === 'string') {
this._bodyText = body;
@@ -280,28 +292,29 @@
return Promise.resolve(new Blob([this._bodyText]))
}
};
+ }
- this.arrayBuffer = function() {
- if (this._bodyArrayBuffer) {
- var isConsumed = consumed(this);
- if (isConsumed) {
- return isConsumed
- }
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
- return Promise.resolve(
- this._bodyArrayBuffer.buffer.slice(
- this._bodyArrayBuffer.byteOffset,
- this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
- )
+ this.arrayBuffer = function() {
+ if (this._bodyArrayBuffer) {
+ var isConsumed = consumed(this);
+ if (isConsumed) {
+ return isConsumed
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
+ return Promise.resolve(
+ this._bodyArrayBuffer.buffer.slice(
+ this._bodyArrayBuffer.byteOffset,
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
)
- } else {
- return Promise.resolve(this._bodyArrayBuffer)
- }
+ )
} else {
- return this.blob().then(readBlobAsArrayBuffer)
+ return Promise.resolve(this._bodyArrayBuffer)
}
- };
- }
+ } else if (support.blob) {
+ return this.blob().then(readBlobAsArrayBuffer)
+ } else {
+ throw new Error('could not read as ArrayBuffer')
+ }
+ };
this.text = function() {
var rejected = consumed(this);
@@ -334,7 +347,7 @@
}
// HTTP methods whose capitalization should be normalized
- var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
+ var methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'];
function normalizeMethod(method) {
var upcased = method.toUpperCase();
@@ -375,7 +388,12 @@
}
this.method = normalizeMethod(options.method || this.method || 'GET');
this.mode = options.mode || this.mode || null;
- this.signal = options.signal || this.signal;
+ this.signal = options.signal || this.signal || (function () {
+ if ('AbortController' in g) {
+ var ctrl = new AbortController();
+ return ctrl.signal;
+ }
+ }());
this.referrer = null;
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
@@ -424,14 +442,26 @@
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
- preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
- var parts = line.split(':');
- var key = parts.shift().trim();
- if (key) {
- var value = parts.join(':').trim();
- headers.append(key, value);
- }
- });
+ // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill
+ // https://github.com/github/fetch/issues/748
+ // https://github.com/zloirock/core-js/issues/751
+ preProcessedHeaders
+ .split('\r')
+ .map(function(header) {
+ return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header
+ })
+ .forEach(function(line) {
+ var parts = line.split(':');
+ var key = parts.shift().trim();
+ if (key) {
+ var value = parts.join(':').trim();
+ try {
+ headers.append(key, value);
+ } catch (error) {
+ console.warn('Response ' + error.message);
+ }
+ }
+ });
return headers
}
@@ -447,8 +477,11 @@
this.type = 'default';
this.status = options.status === undefined ? 200 : options.status;
+ if (this.status < 200 || this.status > 599) {
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].")
+ }
this.ok = this.status >= 200 && this.status < 300;
- this.statusText = 'statusText' in options ? options.statusText : '';
+ this.statusText = options.statusText === undefined ? '' : '' + options.statusText;
this.headers = new Headers(options.headers);
this.url = options.url || '';
this._initBody(bodyInit);
@@ -466,7 +499,9 @@
};
Response.error = function() {
- var response = new Response(null, {status: 0, statusText: ''});
+ var response = new Response(null, {status: 200, statusText: ''});
+ response.ok = false;
+ response.status = 0;
response.type = 'error';
return response
};
@@ -481,14 +516,17 @@
return new Response(null, {status: status, headers: {location: url}})
};
- exports.DOMException = function(message, name) {
- this.message = message;
- this.name = name;
- var error = Error(message);
- this.stack = error.stack;
- };
- exports.DOMException.prototype = Object.create(Error.prototype);
- exports.DOMException.prototype.constructor = exports.DOMException;
+ exports.DOMException = g.DOMException;
+ if (!exports.DOMException) {
+ exports.DOMException = function(message, name) {
+ this.message = message;
+ this.name = name;
+ var error = Error(message);
+ this.stack = error.stack;
+ };
+ exports.DOMException.prototype = Object.create(Error.prototype);
+ exports.DOMException.prototype.constructor = exports.DOMException;
+ }
function fetch(input, init) {
return new Promise(function(resolve, reject) {
@@ -506,10 +544,16 @@
xhr.onload = function() {
var options = {
- status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
};
+ // This check if specifically for when a user fetches a file locally from the file system
+ // Only if the status is out of a normal range
+ if (request.url.indexOf('file://') === 0 && (xhr.status < 200 || xhr.status > 599)) {
+ options.status = 200;
+ } else {
+ options.status = xhr.status;
+ }
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
var body = 'response' in xhr ? xhr.response : xhr.responseText;
setTimeout(function() {
@@ -525,7 +569,7 @@
xhr.ontimeout = function() {
setTimeout(function() {
- reject(new TypeError('Network request failed'));
+ reject(new TypeError('Network request timed out'));
}, 0);
};
@@ -537,7 +581,7 @@
function fixUrl(url) {
try {
- return url === '' && global.location.href ? global.location.href : url
+ return url === '' && g.location.href ? g.location.href : url
} catch (e) {
return url
}
@@ -556,6 +600,7 @@
xhr.responseType = 'blob';
} else if (
support.arrayBuffer &&
+ // TODO: This part is kept as before intentionally, because it fails in ReactUnity for some reason
request.headers.get('Content-Type') &&
request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1
) {
@@ -563,10 +608,17 @@
}
}
- if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) {
+ if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers || (g.Headers && init.headers instanceof g.Headers))) {
+ var names = [];
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
+ names.push(normalizeName(name));
xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
});
+ request.headers.forEach(function(value, name) {
+ if (names.indexOf(name) === -1) {
+ xhr.setRequestHeader(name, value);
+ }
+ });
} else {
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value);
@@ -590,11 +642,11 @@
fetch.polyfill = true;
- if (!global.fetch) {
- global.fetch = fetch;
- global.Headers = Headers;
- global.Request = Request;
- global.Response = Response;
+ if (!g.fetch) {
+ g.fetch = fetch;
+ g.Headers = Headers;
+ g.Request = Request;
+ g.Response = Response;
}
exports.Headers = Headers;
diff --git a/Runtime/Scripting/EngineCapabilities.cs b/Runtime/Scripting/EngineCapabilities.cs
index 7adf7ceb..a4187a5d 100644
--- a/Runtime/Scripting/EngineCapabilities.cs
+++ b/Runtime/Scripting/EngineCapabilities.cs
@@ -15,5 +15,6 @@ public enum EngineCapabilities
URL = 64,
Navigator = 128,
Encoding = 256,
+ AbortController = 512,
}
}
diff --git a/Runtime/Scripting/QuickJS/QuickJSEngine.cs b/Runtime/Scripting/QuickJS/QuickJSEngine.cs
index 9a5408c9..45145986 100644
--- a/Runtime/Scripting/QuickJS/QuickJSEngine.cs
+++ b/Runtime/Scripting/QuickJS/QuickJSEngine.cs
@@ -27,6 +27,7 @@ public class QuickJSEngine : IJavaScriptEngine
| EngineCapabilities.WebSocket
| EngineCapabilities.Console
| EngineCapabilities.Base64
+ | EngineCapabilities.AbortController
#endif
| EngineCapabilities.None;
diff --git a/Runtime/Scripting/ScriptContext.cs b/Runtime/Scripting/ScriptContext.cs
index 35e8fd7e..2bb7e614 100644
--- a/Runtime/Scripting/ScriptContext.cs
+++ b/Runtime/Scripting/ScriptContext.cs
@@ -208,6 +208,9 @@ static void CreatePolyfills(IJavaScriptEngine engine)
if (!engine.Capabilities.HasFlag(EngineCapabilities.Fetch))
engine.Execute(ResourcesHelper.GetPolyfill("fetch"), "ReactUnity/polyfills/fetch");
+
+ if (!engine.Capabilities.HasFlag(EngineCapabilities.AbortController))
+ engine.Execute(ResourcesHelper.GetPolyfill("abortcontroller"), "ReactUnity/polyfills/abortcontroller");
}
static void CreateScheduler(IJavaScriptEngine engine, ReactContext context)
diff --git a/Tests/Runtime/Base/ReactiveTests.cs b/Tests/Runtime/Base/ReactiveTests.cs
index f3eabeb1..18f2b4c4 100644
--- a/Tests/Runtime/Base/ReactiveTests.cs
+++ b/Tests/Runtime/Base/ReactiveTests.cs
@@ -204,6 +204,43 @@ public IEnumerator TestReactiveList()
Assert.AreEqual("0", text.text);
}
+ public class MyClass
+ {
+ public int value = 0;
+ public MyClass(int v) { this.value = v; }
+ }
+
+ [UGUITest(Script = @"
+ export function App() {
+ const globals = ReactUnity.useGlobals();
+ const w = ReactUnity.useReactiveValue(globals.testReactive);
+ return {w?.[0]?.value};
+ }
+ ")]
+ public IEnumerator TestReactiveListIndexedAccess()
+ {
+ yield return null;
+
+ var text = (Host.QuerySelector("text") as UGUI.TextComponent).Text;
+ Assert.AreEqual("", text.text);
+
+
+ var reactive = new ReactiveList() {
+ new MyClass(1),
+ new MyClass(2),
+ };
+
+ Globals.Set("testReactive", reactive);
+ yield return null;
+ yield return null;
+ Assert.AreEqual("1", text.text);
+
+ reactive.RemoveAt(0);
+ yield return null;
+ yield return null;
+ Assert.AreEqual("2", text.text);
+ }
+
[UGUITest(Script = @"
export function App() {
const globals = ReactUnity.useGlobals();