refactor: rename ployfill to polyfill

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
2021-03-26 16:17:13 +08:00
parent 67aa4302e0
commit 93f44e5a00
12 changed files with 18 additions and 13 deletions

View File

@ -0,0 +1 @@
../../.npmignore

View File

@ -0,0 +1,28 @@
{
"name": "@ccms/polyfill",
"version": "0.13.0",
"description": "MiaoScript polyfill package",
"author": "MiaoWoo <admin@yumc.pw>",
"homepage": "https://github.com/circlecloud/ms.git",
"license": "ISC",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"clean": "rimraf dist",
"watch": "tsc --watch",
"build": "yarn clean && tsc",
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@ccms/i18n": "^0.13.0",
"@ccms/nodejs": "^0.13.0",
"@ccms/nashorn": "^0.13.0",
"core-js": "^3.7.0"
},
"devDependencies": {
"@ccms/nashorn": "^0.13.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^4.0.5"
}
}

View File

@ -0,0 +1,26 @@
// ES2015 String ployfill must force overwrite to js method
Object.defineProperty(String.prototype, 'contains', {
value: require(`es5-ext/string/#/contains/shim`),
configurable: true,
enumerable: false,
writable: true
})
Object.defineProperty(String.prototype, 'repeat', {
value: require(`es5-ext/string/#/repeat/shim`),
configurable: true,
enumerable: false,
writable: true
})
Object.defineProperty(String.prototype, 'startsWith', {
value: require(`es5-ext/string/#/starts-with/shim`),
configurable: true,
enumerable: false,
writable: true
})
Object.defineProperty(String.prototype, 'endsWith', {
value: require(`es5-ext/string/#/ends-with/shim`),
configurable: true,
enumerable: false,
writable: true
})

View File

@ -0,0 +1,15 @@
/// <reference types="@ccms/nashorn" />
import '@ccms/nodejs'
import i18n from '@ccms/i18n'
let polyfillStartTime = new Date().getTime()
i18n.initialize()
console.i18n("ms.polyfill.initialize")
import './es5-ext'
import './node-shim'
import 'core-js'
//@ts-ignore
process.on('exit', () => require.disable())
global.setGlobal('Proxy', require('./proxy').Proxy)
global.setGlobal('XMLHttpRequest', require('./xml-http-request').XMLHttpRequest)
global.setGlobal('Blob', require('blob-polyfill').Blob)
console.i18n("ms.polyfill.completed", { time: (new Date().getTime() - polyfillStartTime) / 1000 })

View File

@ -0,0 +1,207 @@
import { EventEmitter } from 'events'
const System = Java.type('java.lang.System')
const Thread = Java.type('java.lang.Thread')
const InterruptedException = Java.type('java.lang.InterruptedException')
const ThreadGroup = Java.type("java.lang.ThreadGroup")
const AtomicInteger = Java.type("java.util.concurrent.atomic.AtomicInteger")
const Callable = Java.type('java.util.concurrent.Callable')
const ThreadFactory = Java.type("java.util.concurrent.ThreadFactory")
const TimeoutException = Java.type('java.util.concurrent.TimeoutException')
const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor')
const LinkedBlockingQueue = Java.type("java.util.concurrent.LinkedBlockingQueue")
const TimeUnit = Java.type('java.util.concurrent.TimeUnit')
const DelayQueue = Java.type('java.util.concurrent.DelayQueue')
const JavaScriptTask = Java.type(base.getJavaScriptTaskClass().name)
const threadCount = new AtomicInteger(0)
const threadGroup = new ThreadGroup("@ccms/ployfill-micro-task")
const microTaskPool = new ThreadPoolExecutor(
100, 200, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue(300),
new ThreadFactory((run: any) => new Thread(threadGroup, run, "@ccms/micro-task-" + threadCount.incrementAndGet()))
)
class Process extends EventEmitter {
env = {
__noSuchProperty__: (prop) => {
return System.getenv(prop)
}
}
platform = System.getProperty("os.name")
constructor() {
super()
this.on('exit', () => {
console.log(`await microTaskPool termination! queueTask: ${microTaskPool.shutdownNow().size()} remainTask: ${threadGroup.activeCount()}`)
microTaskPool.awaitTermination(3000, TimeUnit.MILLISECONDS)
})
}
on(event: string | symbol, listener: (...args: any[]) => void) {
return super.on(event, (...args) => {
try {
listener(...args)
} catch (error) {
try {
super.emit('error', error)
} catch (error) {
console.ex(error)
}
}
})
}
nextTick(func: Function) {
microTaskPool.execute(func)
}
exit(code: number) {
console.log(`process exit by code ${code}!`)
this.emit('exit', code)
}
toString() {
return "[object process]"
}
}
class EventLoop {
private eventLoopMainThread = undefined
private eventLoopTaskQueue = new DelayQueue()
private taskExecTimeout = 3
private fixedThreadPool = undefined
constructor() {
this.taskExecTimeout = parseInt(process.env.MS_NODE_EVENT_LOOP_TIMEOUT) || 3
this.fixedThreadPool = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue(300),
new ThreadFactory((run: any) => {
let thread = new Thread(run, "@ccms/node-shim/event-loop-exec")
thread.setDaemon(true)
return thread
}))
this.eventLoopMainThread = new Thread(() => {
let task = undefined
try {
while (!this.eventLoopMainThread.isInterrupted()) {
task = this.eventLoopTaskQueue.take()
try {
task.getTask()()
} catch (cause) {
try {
process.emit('error', cause)
} catch (error) {
console.error(cause)
console.ex(cause)
}
}
}
} catch (error) {
console.log(`EventLoop Thread isInterrupted exit! remainTask: ${this.eventLoopTaskQueue.size()}`)
this.eventLoopTaskQueue.clear()
this.eventLoopTaskQueue = undefined
this.timeoutCount = undefined
this.timeoutTasks = undefined
this.intervalCount = undefined
this.intervalTasks = undefined
this.eventLoopMainThread = undefined
}
}, "@ccms/node-shim/event-loop")
this.eventLoopMainThread.setDaemon(true)
process.on('exit', () => {
this.eventLoopMainThread.interrupt()
this.fixedThreadPool.shutdownNow()
console.log(`await fixedThreadPool termination!`)
this.fixedThreadPool.awaitTermination(3000, TimeUnit.MILLISECONDS)
})
}
startEventLoop() {
this.eventLoopMainThread.start()
}
private putDelayTask(id: number, callback: Function, ms: number) {
this.eventLoopTaskQueue.put(new JavaScriptTask(id, callback, ms))
}
private limitTimeTask(name: string, callback: Function, ...args: any[]) {
if (!callback) {
throw new Error(`task ${name} callback function can't be null!`)
}
if (this.fixedThreadPool.isShutdown()) { return console.warn(`FixedThreadPool isTerminated! ignore Task ${name}!`) }
try {
this.fixedThreadPool.submit(new Callable({
call: () => {
try {
callback.apply(undefined, args)
} catch (cause) {
cause = cause.getCause && cause.getCause() || cause
try {
process.emit('error', cause)
} catch (error) {
console.error(cause)
console.ex(cause)
}
}
}
})).get(this.taskExecTimeout, TimeUnit.SECONDS)
} catch (error) {
if (error instanceof InterruptedException) {
return console.warn(`FixedThreadPool isInterrupted exit! Task ${name} exec exit!`)
}
if (error instanceof TimeoutException) {
return console.warn(`Task ${name} => ${callback} exec time greater than ${this.taskExecTimeout}s!`)
}
throw error.getCause && error.getCause() || error
}
}
private timeoutCount = new AtomicInteger(0)
private timeoutTasks = []
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
let taskId = this.timeoutCount.incrementAndGet()
this.timeoutTasks[taskId] = callback
console.trace(`create setTimeout task ${taskId} => ${callback}`)
this.putDelayTask(taskId, () => {
if (this.timeoutTasks[taskId]) {
this.limitTimeTask(`setTimeout-${taskId}`, callback, ...args)
} else {
console.trace(`ignore setTimeout ${ms}ms task ${taskId} because it's cancelled!`)
}
}, ms)
return taskId
}
clearTimeout(taskId: number) {
delete this.timeoutTasks[taskId]
}
private intervalCount = new AtomicInteger(0)
private intervalTasks = []
setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
let taskId = this.intervalCount.incrementAndGet()
this.intervalTasks[taskId] = callback
console.trace(`create setInterval ${ms}ms task ${taskId} => ${callback}`)
let intervalTask = () => {
if (this.intervalTasks[taskId]) {
this.limitTimeTask(`setInterval-${taskId}`, callback, ...args)
this.putDelayTask(taskId, intervalTask, ms)
} else {
console.trace(`ignore setInterval task ${taskId} because it's cancelled!`)
}
}
this.putDelayTask(taskId, intervalTask, ms)
return taskId
}
clearInterval(taskId: number) {
delete this.intervalTasks[taskId]
}
}
global.setGlobal('process', new Process(), {})
Object.defineProperty(process, require('core-js/es/symbol/to-string-tag'), { value: '[object process]' })
const eventLoop = new EventLoop()
Object.defineProperty(process, 'eventLoop', { value: eventLoop })
eventLoop.startEventLoop()
global.setGlobal('queueMicrotask', (func: any) => microTaskPool.execute(func), {})
global.setGlobal('setTimeout', eventLoop.setTimeout.bind(eventLoop), {})
global.setGlobal('clearTimeout', eventLoop.clearTimeout.bind(eventLoop), {})
global.setGlobal('setInterval', eventLoop.setInterval.bind(eventLoop), {})
global.setGlobal('clearInterval', eventLoop.clearInterval.bind(eventLoop), {})
global.setGlobal('setImmediate', (callback: (...args: any[]) => void, ...args: any[]) => eventLoop.setTimeout(callback, 0, ...args), { writable: true })
global.setGlobal('clearImmediate ', eventLoop.clearTimeout.bind(eventLoop), { writable: true })

View File

@ -0,0 +1,23 @@
import type { ProxyHandle } from '@ccms/nashorn'
// Nashorn JSAdapter See https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions#Nashornextensions-JSAdapterconstructor
let createProxy = eval(`
function(handle){ return new JSAdapter(handle) }
`)
export class Proxy {
static newProxy(target: any, handle: Partial<ProxyHandle>): any {
return new Proxy(target, handle)
}
constructor(target: any, handle: Partial<ProxyHandle>) {
return createProxy({
__get__: (name: string) => handle.get ? handle.get(target, name, undefined) : target[name],
__put__: (name: string, value: any) => handle.set ? handle.set(target, name, value, undefined) : target[name] = value,
__call__: (name: string, ...args: any) => handle.apply ? handle.apply(target, name, args) : target[name].apply(target, args),
__new__: (...args: any) => handle.construct ? handle.construct(target, args, target) : new target(...args),
__getIds__: () => handle.ownKeys ? handle.ownKeys(target) : Object.keys(target),
__getValues__: () => handle.values ? handle.values(target) : Object.values(target),
__has__: (name: string) => handle.has ? handle.has(target, name) : Object.getOwnPropertyDescriptor(target, name) != undefined,
__delete__: (name: string) => handle.deleteProperty ? handle.deleteProperty(target, name) : delete target[name]
})
}
}

View File

@ -0,0 +1,141 @@
(function nashornEventLoopMain(context) {
'use strict';
var Thread = Java.type('java.lang.Thread');
var Phaser = Java.type('java.util.concurrent.Phaser');
var ArrayDeque = Java.type('java.util.ArrayDeque');
var HashMap = Java.type('java.util.HashMap');
var TimeUnit = Java.type("java.util.concurrent.TimeUnit");
var Runnable = Java.type('java.lang.Runnable');
var globalTimerId;
var timerMap;
var eventLoop;
var phaser = new Phaser();
// __NASHORN_POLYFILL_TIMER__ type is ScheduledExecutorService
var scheduler = context.__NASHORN_POLYFILL_TIMER__;
resetEventLoop();
// console.log('main javasript thread ' + Thread.currentThread().getName());
function resetEventLoop() {
globalTimerId = 1;
if (timerMap) {
timerMap.forEach(function(key, value) {
value.cancel(true);
})
}
timerMap = new HashMap();
eventLoop = new ArrayDeque();
}
function waitForMessages() {
phaser.register();
var wait = !(eventLoop.size() === 0);
phaser.arriveAndDeregister();
return wait;
}
function processNextMessages() {
var remaining = 1;
while (remaining) {
phaser.register();
var message = eventLoop.removeFirst();
remaining = eventLoop.size();
phaser.arriveAndDeregister();
var fn = message.fn;
var args = message.args;
try {
fn.apply(context, args);
} catch (e) {
console.trace(e);
console.trace(fn);
console.trace(args);
}
}
}
context.nashornEventLoop = {
process: function() {
while (waitForMessages()) {
processNextMessages()
}
},
reset: resetEventLoop
};
function createRunnable(fn, timerId, args, repeated) {
return new Runnable({
run: function() {
try {
var phase = phaser.register();
eventLoop.addLast({
fn: fn,
args: args
});
} catch (e) {
console.trace(e);
} finally {
if (!repeated) timerMap.remove(timerId);
phaser.arriveAndDeregister();
}
}
})
}
var setTimeout = function(fn, millis /* [, args...] */) {
var args = [].slice.call(arguments, 2, arguments.length);
var timerId = globalTimerId++;
var runnable = createRunnable(fn, timerId, args, false);
var task = scheduler.schedule(runnable, millis, TimeUnit.MILLISECONDS);
timerMap.put(timerId, task);
return timerId;
};
var setImmediate = function(fn /* [, args...] */) {
var args = [].slice.call(arguments, 1, arguments.length);
// @ts-ignore
return setTimeout(fn, 0, args);
}
var clearImmediate = function(timerId) {
clearTimeout(timerId);
}
var clearTimeout = function(timerId) {
var task = timerMap.get(timerId);
if (task) {
task.cancel(true);
timerMap.remove(timerId);
}
};
var setInterval = function(fn, delay /* [, args...] */) {
var args = [].slice.call(arguments, 2, arguments.length);
var timerId = globalTimerId++;
var runnable = createRunnable(fn, timerId, args, true);
var task = scheduler.scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS);
timerMap.put(timerId, task);
return timerId;
};
var clearInterval = function(timerId) {
clearTimeout(timerId);
};
context.setTimeout = setTimeout;
context.clearTimeout = clearTimeout;
context.setImmediate = setImmediate;
context.clearImmediate = clearImmediate;
context.setInterval = setInterval;
context.clearInterval = clearInterval;
// @ts-ignore
})(typeof global !== "undefined" && global || typeof self !== "undefined" && self || this);

View File

@ -0,0 +1,255 @@
import '@ccms/nashorn'
const URL = Java.type("java.net.URL")
const Files = Java.type("java.nio.file.Files")
const StandardCopyOption = Java.type("java.nio.file.StandardCopyOption")
const JavaString = Java.type("java.lang.String")
const SecureRandom = Java.type("java.security.SecureRandom")
const SSLContext = Java.type("javax.net.ssl.SSLContext")
const HttpsURLConnection = Java.type("javax.net.ssl.HttpsURLConnection")
const HostnameVerifier = Java.type("javax.net.ssl.HostnameVerifier")
const X509TrustManager = Java.type("javax.net.ssl.X509TrustManager")
const SocketTimeoutException = Java.type('java.net.SocketTimeoutException')
const Callable = Java.type('java.util.concurrent.Callable')
const Executors = Java.type('java.util.concurrent.Executors')
const UTF_8 = "UTF-8"
const TrustAnyHostnameVerifier = new HostnameVerifier({ verify: () => true })
const SSLSocketFactory = function initSSLSocketFactory() {
let sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, [new X509TrustManager({
getAcceptedIssuers: () => null,
checkClientTrusted: () => { },
checkServerTrusted: () => { }
})], new SecureRandom())
return sslContext.getSocketFactory()
}()
interface Future<T> {
cancel(): boolean
isCancelled(): boolean
isDone(): boolean
get(): T
}
enum ReadyState {
UNSENT,//Client has been created. open() not called yet.
OPENED,//open() has been called.
HEADERS_RECEIVED,//send() has been called, and headers and status are available.
LOADING,//Downloading; responseText holds partial data.
DONE,//The operation is complete.
}
type RequestMethod =
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
type ResponseType =
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream'
type EventType =
| 'load'
| 'error'
| 'abort'
| 'progress'
| 'timeout'
| 'loadend'
| 'loadstart'
type HttpHeader = { [key: string]: string }
const executor = Executors.newCachedThreadPool()
export class XMLHttpRequest {
private _timeout: number
private _responseType: ResponseType = 'text';
private _withCredentials: boolean
private _readyState: ReadyState = ReadyState.UNSENT;
private _method: RequestMethod
private _url: string
private _async: boolean
private _mimeType: string
private _requestHeaders: HttpHeader = {};
private _status: number = 0;
private _statusText: string = null;
private _response: any
private _responseText: any
private _responseURL: string
private _responseHeaders: HttpHeader = {};
private _connection = null;
get timeout() {
return this._timeout
}
set timeout(timeout: number) {
this._timeout = timeout
}
get readyState() {
return this._readyState
}
set responseType(type: ResponseType) {
this._responseType = type
}
get responseType() {
return this._responseType
}
get status() {
return this._status
}
get statusText() {
return this._statusText
}
get response() {
return this._response || this.get()
}
get responseText() {
return this._responseText
}
get responseXML() {
return this._response
}
get responseURL() {
return this._responseURL
}
public onload: () => void
public onerror: (ex: Error) => void
public onabort: () => void
public onprogress: () => void
public ontimeout: (ex: Error) => void
public onloadend: () => void
public onloadstart: () => void
public onreadystatechange: () => void
setRequestHeader(key: string, val: string) {
this._requestHeaders[key] = val
}
getResponseHeader(key: string): string {
return this._responseHeaders[key]
}
getAllResponseHeaders(): any {
return this._responseHeaders
}
addEventListener(event: EventType, callback: Function) {
this[`on${event.toLowerCase()}`] = callback
}
overrideMimeType(mimeType: string) {
this._mimeType = mimeType
}
open(method: RequestMethod, url: string, async: boolean = true, user?: string, password?: string) {
if (this._readyState !== ReadyState.UNSENT) { throw new Error(`Error Status ${this._readyState}!`) }
this._method = method
this._url = url
this._async = async
this._connection = new URL(this._url).openConnection()
if (this._connection instanceof HttpsURLConnection) {
this._connection.setHostnameVerifier(TrustAnyHostnameVerifier)
this._connection.setSSLSocketFactory(SSLSocketFactory)
}
this._connection.setRequestMethod(this._method)
this._connection.setDoOutput(true)
this._connection.setDoInput(true)
this._connection.setConnectTimeout(this._timeout)
this._connection.setReadTimeout(this._timeout)
this.setReadyState(ReadyState.OPENED)
}
send(body?: string | object): Future<string> {
for (const header in this._requestHeaders) {
this._connection.setRequestProperty(header, this._requestHeaders[header])
}
if (this._readyState !== ReadyState.OPENED) { throw new Error(`Error Status ${this._readyState}!`) }
let future = executor.submit(new Callable({ call: () => this._send(body) }))
if (!this._async) { future.get() }
return future
}
get() {
if (this._response === undefined) {
switch (this._responseType) {
case "json":
return this._response = JSON.parse(this._responseText)
case "text":
return this._response = this._responseText
default:
throw Error(`Unsupport ResponseType: ${this._responseType} !`)
}
}
return this._response
}
abort() {
this._connection.disconnect()
this.onabort && this.onabort()
}
private _send(body?: string | object) {
try {
this._connection.connect()
this.onloadstart && this.onloadstart()
if (body) {
let bodyType = Object.prototype.toString.call(body)
if (typeof body !== "string") { throw new Error(`body(${bodyType}) must be string!`) }
var out = this._connection.getOutputStream()
out.write(new JavaString(body).getBytes(UTF_8))
out.flush()
out.close()
}
this.setReadyState(ReadyState.LOADING)
this._status = this._connection.getResponseCode()
this._statusText = this._connection.getResponseMessage()
if (this._status >= 0 && this._status < 300) {
this._responseText = this.readOutput(this._connection.getInputStream())
} else if (this._status >= 300 && this._status < 400) {
this._responseURL = this.getResponseHeader('Location')
} else {
this._responseText = this.readOutput(this._connection.getErrorStream())
}
this.setResponseHeaders(this._connection.getHeaderFields())
this.onloadend && this.onloadend()
} catch (ex) {
if (ex instanceof SocketTimeoutException && this.ontimeout) {
return this.ontimeout(ex)
} else if (this.onerror) {
return this.onerror(ex)
}
throw ex
} finally {
this._connection.disconnect()
this.setReadyState(ReadyState.DONE)
}
}
private setResponseHeaders(header: any) {
header.forEach((key: string | number, value: string | any[]) => {
this._responseHeaders[key + ''] = value[value.length - 1] + ''
})
}
private setReadyState(state: ReadyState) {
this._readyState = state
this.onreadystatechange && this.onreadystatechange()
}
private readOutput(input: any) {
var tempFile = Files.createTempFile('xhr', '.response')
Files.copy(input, tempFile, StandardCopyOption['REPLACE_EXISTING']); tempFile.toFile().deleteOnExit()
return new JavaString(Files.readAllBytes(tempFile), 'UTF-8')
}
}

View File

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist"
}
}