2021-01-10 21:59:07 -05:00
|
|
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
2019-04-21 12:16:55 -04:00
|
|
|
/*
|
|
|
|
SharedQueue Binary Layout
|
|
|
|
+-------------------------------+-------------------------------+
|
|
|
|
| NUM_RECORDS (32) |
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
| NUM_SHIFTED_OFF (32) |
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
| HEAD (32) |
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
| OFFSETS (32) |
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
| RECORD_ENDS (*MAX_RECORDS) ...
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
| RECORDS (*MAX_RECORDS) ...
|
|
|
|
+---------------------------------------------------------------+
|
|
|
|
*/
|
2021-02-04 17:18:32 -05:00
|
|
|
"use strict";
|
2019-03-26 08:22:07 -04:00
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
((window) => {
|
2019-03-14 19:17:52 -04:00
|
|
|
const MAX_RECORDS = 100;
|
|
|
|
const INDEX_NUM_RECORDS = 0;
|
|
|
|
const INDEX_NUM_SHIFTED_OFF = 1;
|
|
|
|
const INDEX_HEAD = 2;
|
|
|
|
const INDEX_OFFSETS = 3;
|
2019-08-07 14:02:29 -04:00
|
|
|
const INDEX_RECORDS = INDEX_OFFSETS + 2 * MAX_RECORDS;
|
2019-03-14 19:17:52 -04:00
|
|
|
const HEAD_INIT = 4 * INDEX_RECORDS;
|
|
|
|
|
2019-08-05 07:23:41 -04:00
|
|
|
// Available on start due to bindings.
|
2020-05-12 11:09:28 -04:00
|
|
|
const core = window.Deno.core;
|
|
|
|
const { recv, send } = core;
|
2019-03-26 08:22:07 -04:00
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////// Dispatch /////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const dispatch = send;
|
|
|
|
const dispatchByName = (opName, control, ...zeroCopy) =>
|
|
|
|
dispatch(opsCache[opName], control, ...zeroCopy);
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////// Shared array buffer ///////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2019-03-26 08:22:07 -04:00
|
|
|
let sharedBytes;
|
|
|
|
let shared32;
|
2020-01-17 08:26:11 -05:00
|
|
|
|
2020-06-21 10:34:43 -04:00
|
|
|
let opsCache = {};
|
2019-04-24 21:43:06 -04:00
|
|
|
|
|
|
|
function init() {
|
2020-05-12 11:09:28 -04:00
|
|
|
const shared = core.shared;
|
2019-04-24 21:43:06 -04:00
|
|
|
assert(shared.byteLength > 0);
|
|
|
|
assert(sharedBytes == null);
|
|
|
|
assert(shared32 == null);
|
|
|
|
sharedBytes = new Uint8Array(shared);
|
|
|
|
shared32 = new Int32Array(shared);
|
2021-03-20 12:51:08 -04:00
|
|
|
|
2020-01-17 08:26:11 -05:00
|
|
|
asyncHandlers = [];
|
2020-05-12 11:09:28 -04:00
|
|
|
// Callers should not call core.recv, use setAsyncHandler.
|
|
|
|
recv(handleAsyncMsgFromRust);
|
2019-04-24 21:43:06 -04:00
|
|
|
}
|
2019-03-14 19:17:52 -04:00
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
function ops() {
|
2020-01-02 07:48:46 -05:00
|
|
|
// op id 0 is a special value to retrieve the map of registered ops.
|
2020-08-21 05:47:57 -04:00
|
|
|
const opsMapBytes = send(0);
|
2019-09-30 14:59:44 -04:00
|
|
|
const opsMapJson = String.fromCharCode.apply(null, opsMapBytes);
|
2020-06-21 10:34:43 -04:00
|
|
|
opsCache = JSON.parse(opsMapJson);
|
|
|
|
return { ...opsCache };
|
2019-09-30 14:59:44 -04:00
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
function assert(cond) {
|
|
|
|
if (!cond) {
|
|
|
|
throw Error("assert");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function reset() {
|
2019-03-15 15:49:41 -04:00
|
|
|
shared32[INDEX_NUM_RECORDS] = 0;
|
|
|
|
shared32[INDEX_NUM_SHIFTED_OFF] = 0;
|
2019-03-14 19:17:52 -04:00
|
|
|
shared32[INDEX_HEAD] = HEAD_INIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
function head() {
|
|
|
|
return shared32[INDEX_HEAD];
|
|
|
|
}
|
|
|
|
|
|
|
|
function numRecords() {
|
|
|
|
return shared32[INDEX_NUM_RECORDS];
|
|
|
|
}
|
|
|
|
|
2019-03-15 15:49:41 -04:00
|
|
|
function size() {
|
|
|
|
return shared32[INDEX_NUM_RECORDS] - shared32[INDEX_NUM_SHIFTED_OFF];
|
|
|
|
}
|
|
|
|
|
2019-08-07 14:02:29 -04:00
|
|
|
function setMeta(index, end, opId) {
|
|
|
|
shared32[INDEX_OFFSETS + 2 * index] = end;
|
|
|
|
shared32[INDEX_OFFSETS + 2 * index + 1] = opId;
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
2019-08-07 14:02:29 -04:00
|
|
|
function getMeta(index) {
|
2020-11-21 08:19:43 -05:00
|
|
|
if (index >= numRecords()) {
|
2019-03-14 19:17:52 -04:00
|
|
|
return null;
|
|
|
|
}
|
2020-11-21 08:19:43 -05:00
|
|
|
const buf = shared32[INDEX_OFFSETS + 2 * index];
|
|
|
|
const opId = shared32[INDEX_OFFSETS + 2 * index + 1];
|
|
|
|
return [opId, buf];
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function getOffset(index) {
|
2020-11-21 08:19:43 -05:00
|
|
|
if (index >= numRecords()) {
|
2019-03-14 19:17:52 -04:00
|
|
|
return null;
|
|
|
|
}
|
2020-11-21 08:19:43 -05:00
|
|
|
if (index == 0) {
|
|
|
|
return HEAD_INIT;
|
|
|
|
}
|
|
|
|
const prevEnd = shared32[INDEX_OFFSETS + 2 * (index - 1)];
|
|
|
|
return (prevEnd + 3) & ~3;
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
2019-08-07 14:02:29 -04:00
|
|
|
function push(opId, buf) {
|
2019-09-07 12:27:18 -04:00
|
|
|
const off = head();
|
|
|
|
const end = off + buf.byteLength;
|
2020-03-01 17:17:59 -05:00
|
|
|
const alignedEnd = (end + 3) & ~3;
|
2019-09-07 12:27:18 -04:00
|
|
|
const index = numRecords();
|
2020-11-21 08:19:43 -05:00
|
|
|
const shouldNotPush = alignedEnd > shared32.byteLength ||
|
|
|
|
index >= MAX_RECORDS;
|
|
|
|
if (shouldNotPush) {
|
2019-05-20 12:06:57 -04:00
|
|
|
// console.log("shared_queue.js push fail");
|
2019-03-14 19:17:52 -04:00
|
|
|
return false;
|
|
|
|
}
|
2019-08-07 14:02:29 -04:00
|
|
|
setMeta(index, end, opId);
|
2020-03-01 17:17:59 -05:00
|
|
|
assert(alignedEnd % 4 === 0);
|
2019-03-14 19:17:52 -04:00
|
|
|
assert(end - off == buf.byteLength);
|
|
|
|
sharedBytes.set(buf, off);
|
|
|
|
shared32[INDEX_NUM_RECORDS] += 1;
|
2020-03-01 17:17:59 -05:00
|
|
|
shared32[INDEX_HEAD] = alignedEnd;
|
2019-03-14 19:17:52 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns null if empty.
|
|
|
|
function shift() {
|
2019-09-07 12:27:18 -04:00
|
|
|
const i = shared32[INDEX_NUM_SHIFTED_OFF];
|
2019-03-15 15:49:41 -04:00
|
|
|
if (size() == 0) {
|
|
|
|
assert(i == 0);
|
2019-03-14 19:17:52 -04:00
|
|
|
return null;
|
|
|
|
}
|
2019-03-15 15:49:41 -04:00
|
|
|
|
2019-08-07 14:02:29 -04:00
|
|
|
const off = getOffset(i);
|
|
|
|
const [opId, end] = getMeta(i);
|
2019-03-14 19:17:52 -04:00
|
|
|
|
2019-03-15 15:49:41 -04:00
|
|
|
if (size() > 1) {
|
|
|
|
shared32[INDEX_NUM_SHIFTED_OFF] += 1;
|
|
|
|
} else {
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2019-03-26 08:22:07 -04:00
|
|
|
assert(off != null);
|
|
|
|
assert(end != null);
|
2019-08-07 14:02:29 -04:00
|
|
|
const buf = sharedBytes.subarray(off, end);
|
|
|
|
return [opId, buf];
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////// Error handling //////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const errorMap = {};
|
|
|
|
|
|
|
|
function registerErrorClass(errorName, className, args) {
|
|
|
|
if (typeof errorMap[errorName] !== "undefined") {
|
|
|
|
throw new TypeError(`Error class for "${errorName}" already registered`);
|
|
|
|
}
|
|
|
|
errorMap[errorName] = [className, args ?? []];
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleError(className, message) {
|
|
|
|
if (typeof errorMap[className] === "undefined") {
|
|
|
|
return new Error(
|
|
|
|
`Unregistered error class: "${className}"\n` +
|
|
|
|
` ${message}\n` +
|
|
|
|
` Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const [ErrorClass, args] = errorMap[className];
|
|
|
|
return new ErrorClass(message, ...args);
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////// Async handling //////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
let asyncHandlers = [];
|
|
|
|
|
2020-01-17 08:26:11 -05:00
|
|
|
function setAsyncHandler(opId, cb) {
|
|
|
|
assert(opId != null);
|
|
|
|
asyncHandlers[opId] = cb;
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
2021-02-23 07:08:50 -05:00
|
|
|
function handleAsyncMsgFromRust() {
|
2020-11-21 08:19:43 -05:00
|
|
|
while (true) {
|
|
|
|
const opIdBuf = shift();
|
|
|
|
if (opIdBuf == null) {
|
|
|
|
break;
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
2020-11-21 08:19:43 -05:00
|
|
|
assert(asyncHandlers[opIdBuf[0]] != null);
|
2021-03-20 12:51:08 -04:00
|
|
|
asyncHandlers[opIdBuf[0]](opIdBuf[1], true);
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
2021-02-23 07:08:50 -05:00
|
|
|
|
|
|
|
for (let i = 0; i < arguments.length; i += 2) {
|
2021-03-20 12:51:08 -04:00
|
|
|
asyncHandlers[arguments[i]](arguments[i + 1], false);
|
2021-02-23 07:08:50 -05:00
|
|
|
}
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////// General sync & async ops handling ////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
2020-06-21 10:34:43 -04:00
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
let nextRequestId = 1;
|
|
|
|
const promiseTable = {};
|
|
|
|
|
|
|
|
function asyncHandle(u8Array, isCopyNeeded, opResultParser) {
|
|
|
|
const [requestId, result, error] = opResultParser(u8Array, isCopyNeeded);
|
|
|
|
if (error !== null) {
|
|
|
|
promiseTable[requestId][1](error);
|
|
|
|
} else {
|
|
|
|
promiseTable[requestId][0](result);
|
2020-08-07 16:47:18 -04:00
|
|
|
}
|
2021-03-20 12:51:08 -04:00
|
|
|
delete promiseTable[requestId];
|
2020-08-07 16:47:18 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
function opAsync(opName, opRequestBuilder, opResultParser) {
|
|
|
|
const opId = opsCache[opName];
|
|
|
|
// Make sure requests of this type are handled by the asyncHandler
|
|
|
|
// The asyncHandler's role is to call the "promiseTable[requestId]" function
|
|
|
|
if (typeof asyncHandlers[opId] === "undefined") {
|
|
|
|
asyncHandlers[opId] = (buffer, isCopyNeeded) =>
|
|
|
|
asyncHandle(buffer, isCopyNeeded, opResultParser);
|
|
|
|
}
|
|
|
|
|
|
|
|
const requestId = nextRequestId++;
|
|
|
|
|
|
|
|
// Create and store promise
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
|
|
promiseTable[requestId] = [resolve, reject];
|
|
|
|
});
|
|
|
|
|
|
|
|
// Synchronously dispatch async request
|
|
|
|
core.dispatch(opId, ...opRequestBuilder(requestId));
|
|
|
|
|
|
|
|
// Wait for async response
|
|
|
|
return promise;
|
2020-08-07 16:47:18 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
function opSync(opName, opRequestBuilder, opResultParser) {
|
|
|
|
const opId = opsCache[opName];
|
|
|
|
const u8Array = core.dispatch(opId, ...opRequestBuilder());
|
|
|
|
|
|
|
|
const [_, result, error] = opResultParser(u8Array, false);
|
|
|
|
if (error !== null) throw error;
|
|
|
|
return result;
|
2020-08-20 09:45:59 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////// Bin ops handling /////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const binRequestHeaderByteLength = 8 + 4;
|
|
|
|
const scratchBuffer = new ArrayBuffer(binRequestHeaderByteLength);
|
|
|
|
const scratchView = new DataView(scratchBuffer);
|
|
|
|
|
|
|
|
function binOpBuildRequest(requestId, argument, zeroCopy) {
|
|
|
|
scratchView.setBigUint64(0, BigInt(requestId), true);
|
|
|
|
scratchView.setUint32(8, argument, true);
|
|
|
|
return [scratchView, ...zeroCopy];
|
2020-08-20 09:45:59 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
function binOpParseResult(u8Array, isCopyNeeded) {
|
|
|
|
// Decode header value from u8Array
|
|
|
|
const headerByteLength = 8 + 2 * 4;
|
|
|
|
assert(u8Array.byteLength >= headerByteLength);
|
|
|
|
assert(u8Array.byteLength % 4 == 0);
|
|
|
|
const view = new DataView(
|
|
|
|
u8Array.buffer,
|
|
|
|
u8Array.byteOffset + u8Array.byteLength - headerByteLength,
|
|
|
|
headerByteLength,
|
|
|
|
);
|
|
|
|
|
|
|
|
const requestId = Number(view.getBigUint64(0, true));
|
|
|
|
const status = view.getUint32(8, true);
|
|
|
|
const result = view.getUint32(12, true);
|
|
|
|
|
|
|
|
// Error handling
|
|
|
|
if (status !== 0) {
|
|
|
|
const className = core.decode(u8Array.subarray(0, result));
|
|
|
|
const message = core.decode(u8Array.subarray(result, -headerByteLength))
|
|
|
|
.trim();
|
|
|
|
|
|
|
|
return [requestId, null, handleError(className, message)];
|
|
|
|
}
|
2020-08-20 09:45:59 -04:00
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
if (u8Array.byteLength === headerByteLength) {
|
|
|
|
return [requestId, result, null];
|
2020-10-05 05:35:51 -04:00
|
|
|
}
|
2021-03-20 12:51:08 -04:00
|
|
|
|
|
|
|
// Rest of response buffer is passed as reference or as a copy
|
|
|
|
let respBuffer = null;
|
|
|
|
if (isCopyNeeded) {
|
|
|
|
// Copy part of the response array (if sent through shared array buf)
|
|
|
|
respBuffer = u8Array.slice(0, result);
|
|
|
|
} else {
|
|
|
|
// Create view on existing array (if sent through overflow)
|
|
|
|
respBuffer = u8Array.subarray(0, result);
|
2020-11-21 08:19:43 -05:00
|
|
|
}
|
2021-03-20 12:51:08 -04:00
|
|
|
|
|
|
|
return [requestId, respBuffer, null];
|
2020-10-05 05:35:51 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
function binOpAsync(opName, argument = 0, ...zeroCopy) {
|
|
|
|
return opAsync(
|
|
|
|
opName,
|
|
|
|
(requestId) => binOpBuildRequest(requestId, argument, zeroCopy),
|
|
|
|
binOpParseResult,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function binOpSync(opName, argument = 0, ...zeroCopy) {
|
|
|
|
return opSync(
|
|
|
|
opName,
|
|
|
|
() => binOpBuildRequest(0, argument, zeroCopy),
|
|
|
|
binOpParseResult,
|
|
|
|
);
|
2020-08-20 09:45:59 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////// Json ops handling ////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const jsonRequestHeaderLength = 8;
|
|
|
|
|
|
|
|
function jsonOpBuildRequest(requestId, argument, zeroCopy) {
|
|
|
|
const u8Array = core.encode(
|
|
|
|
"\0".repeat(jsonRequestHeaderLength) + JSON.stringify(argument),
|
|
|
|
);
|
|
|
|
new DataView(u8Array.buffer).setBigUint64(0, BigInt(requestId), true);
|
|
|
|
return [u8Array, ...zeroCopy];
|
2020-08-20 09:45:59 -04:00
|
|
|
}
|
|
|
|
|
2021-03-20 12:51:08 -04:00
|
|
|
function jsonOpParseResult(u8Array, _) {
|
|
|
|
const data = JSON.parse(core.decode(u8Array));
|
|
|
|
|
|
|
|
if ("err" in data) {
|
|
|
|
return [
|
|
|
|
data.requestId,
|
|
|
|
null,
|
|
|
|
handleError(data.err.className, data.err.message),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [data.requestId, data.ok, null];
|
|
|
|
}
|
|
|
|
|
|
|
|
function jsonOpAsync(opName, argument = null, ...zeroCopy) {
|
|
|
|
return opAsync(
|
|
|
|
opName,
|
|
|
|
(requestId) => jsonOpBuildRequest(requestId, argument, zeroCopy),
|
|
|
|
jsonOpParseResult,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function jsonOpSync(opName, argument = null, ...zeroCopy) {
|
|
|
|
return opSync(
|
|
|
|
opName,
|
|
|
|
() => [core.encode(JSON.stringify(argument)), ...zeroCopy],
|
|
|
|
jsonOpParseResult,
|
|
|
|
);
|
2020-08-20 09:45:59 -04:00
|
|
|
}
|
|
|
|
|
2020-09-17 12:09:50 -04:00
|
|
|
function resources() {
|
|
|
|
return jsonOpSync("op_resources");
|
|
|
|
}
|
|
|
|
function close(rid) {
|
2021-03-20 12:51:08 -04:00
|
|
|
return jsonOpSync("op_close", { rid });
|
2020-09-17 12:09:50 -04:00
|
|
|
}
|
|
|
|
|
2020-05-12 11:09:28 -04:00
|
|
|
Object.assign(window.Deno.core, {
|
2020-08-20 09:45:59 -04:00
|
|
|
jsonOpAsync,
|
|
|
|
jsonOpSync,
|
2021-03-20 12:51:08 -04:00
|
|
|
binOpAsync,
|
|
|
|
binOpSync,
|
|
|
|
dispatch,
|
|
|
|
dispatchByName,
|
2020-05-12 11:09:28 -04:00
|
|
|
ops,
|
2020-09-17 12:09:50 -04:00
|
|
|
close,
|
|
|
|
resources,
|
2020-08-07 16:47:18 -04:00
|
|
|
registerErrorClass,
|
2021-01-05 16:10:50 -05:00
|
|
|
sharedQueueInit: init,
|
2020-05-12 11:09:28 -04:00
|
|
|
// sharedQueue is private but exposed for testing.
|
2019-03-26 08:22:07 -04:00
|
|
|
sharedQueue: {
|
2019-04-21 12:16:55 -04:00
|
|
|
MAX_RECORDS,
|
2019-03-14 19:17:52 -04:00
|
|
|
head,
|
|
|
|
numRecords,
|
|
|
|
size,
|
|
|
|
push,
|
|
|
|
reset,
|
2020-03-28 13:03:49 -04:00
|
|
|
shift,
|
2019-09-30 14:59:44 -04:00
|
|
|
},
|
2021-03-20 12:51:08 -04:00
|
|
|
// setAsyncHandler is private but exposed for testing.
|
|
|
|
setAsyncHandler,
|
2020-05-12 11:09:28 -04:00
|
|
|
});
|
2019-04-24 21:43:06 -04:00
|
|
|
})(this);
|