[adapters] 增加小游戏适配部分源码

This commit is contained in:
SmallMain
2024-10-16 17:12:08 +08:00
parent 887d4a96c9
commit 07bf3b7a96
345 changed files with 38447 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
import HTMLAudioElement from './HTMLAudioElement'
const HAVE_NOTHING = 0
const HAVE_METADATA = 1
const HAVE_CURRENT_DATA = 2
const HAVE_FUTURE_DATA = 3
const HAVE_ENOUGH_DATA = 4
let SN_SEED = 1
const _innerAudioContextMap = {}
export default class Audio extends HTMLAudioElement {
constructor(url) {
super()
this._$sn = SN_SEED++;
this.HAVE_NOTHING = HAVE_NOTHING
this.HAVE_METADATA = HAVE_METADATA
this.HAVE_CURRENT_DATA = HAVE_CURRENT_DATA
this.HAVE_FUTURE_DATA = HAVE_FUTURE_DATA
this.HAVE_ENOUGH_DATA = HAVE_ENOUGH_DATA
this.readyState = HAVE_NOTHING
const innerAudioContext = my.createInnerAudioContext()
_innerAudioContextMap[this._$sn] = innerAudioContext
this._canplayEvents = [
'load',
'loadend',
'canplay',
'canplaythrough',
'loadedmetadata'
]
innerAudioContext.onCanplay(() => {
this._loaded = true
this.readyState = this.HAVE_CURRENT_DATA
this._canplayEvents.forEach((type) => {
this.dispatchEvent({ type: type })
})
})
innerAudioContext.onPlay(() => {
this._paused = _innerAudioContextMap[this._$sn].paused
this.dispatchEvent({ type: 'play' })
})
innerAudioContext.onPause(() => {
this._paused = _innerAudioContextMap[this._$sn].paused
this.dispatchEvent({ type: 'pause' })
})
innerAudioContext.onEnded(() => {
this._paused = _innerAudioContextMap[this._$sn].paused
if (_innerAudioContextMap[this._$sn].loop === false) {
this.dispatchEvent({ type: 'ended' })
}
this.readyState = HAVE_ENOUGH_DATA
})
innerAudioContext.onError(() => {
this._paused = _innerAudioContextMap[this._$sn].paused
this.dispatchEvent({ type: 'error' })
})
if (url) {
this.src = url
} else {
this._src = ''
}
this._loop = innerAudioContext.loop
this._autoplay = innerAudioContext.autoplay
this._paused = innerAudioContext.paused
this._volume = innerAudioContext.volume
this._muted = false;
}
addEventListener(type, listener, options = {}) {
super.addEventListener(type, listener, options)
type = String(type).toLowerCase()
if (this._loaded && this._canplayEvents.indexOf(type) !== -1) {
this.dispatchEvent({ type: type })
}
}
load() {
// console.warn('HTMLAudioElement.load() is not implemented.')
// weixin doesn't need call load() manually
}
play() {
_innerAudioContextMap[this._$sn].play()
}
resume() {
_innerAudioContextMap[this._$sn].resume()
}
pause() {
_innerAudioContextMap[this._$sn].pause()
}
stop() {
_innerAudioContextMap[this._$sn].stop()
}
destroy() {
_innerAudioContextMap[this._$sn].destroy()
}
canPlayType(mediaType = '') {
if (typeof mediaType !== 'string') {
return ''
}
if (mediaType.indexOf('audio/mpeg') > -1 || mediaType.indexOf('audio/mp4')) {
return 'probably'
}
return ''
}
get currentTime() {
return _innerAudioContextMap[this._$sn].currentTime
}
set currentTime(value) {
_innerAudioContextMap[this._$sn].seek(value)
}
get duration () {
return _innerAudioContextMap[this._$sn].duration
}
get src() {
return this._src
}
set src(value) {
this._src = value
this._loaded = false
this.readyState = this.HAVE_NOTHING
const innerAudioContext = _innerAudioContextMap[this._$sn]
innerAudioContext.src = value
}
get loop() {
return this._loop
}
set loop(value) {
this._loop = value
_innerAudioContextMap[this._$sn].loop = value
}
get autoplay() {
return this.autoplay
}
set autoplay(value) {
this._autoplay = value
_innerAudioContextMap[this._$sn].autoplay = value
}
get paused() {
return this._paused;
}
get volume() {
return this._volume;
}
set volume(value) {
this._volume = value;
if (!this._muted) {
_innerAudioContextMap[this._$sn].volume = value;
}
}
get muted() {
return this._muted;
}
set muted(value) {
this._muted = value;
if (value) {
_innerAudioContextMap[this._$sn].volume = 0;
} else {
_innerAudioContextMap[this._$sn].volume = this._volume;
}
}
cloneNode() {
const newAudio = new Audio()
newAudio.loop = this.loop
newAudio.autoplay = this.autoplay
newAudio.src = this.src
return newAudio
}
}

View File

@@ -0,0 +1,77 @@
import { innerWidth, innerHeight } from './WindowProperties'
function Canvas () {}
let CanvasProxy = new Proxy(Canvas, {
construct () {
const canvas = my.createCanvas()
canvas.type = 'canvas'
// canvas.__proto__.__proto__.__proto__ = new HTMLCanvasElement()
const _getContext = canvas.getContext
canvas.getBoundingClientRect = () => {
const ret = {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
}
return ret
}
canvas.style = {
top: '0px',
left: '0px',
width: innerWidth + 'px',
height: innerHeight + 'px',
}
canvas.addEventListener = function (type, listener, options = {}) {
// console.log('canvas.addEventListener', type);
if (my.isIDE) {
$global.document.addEventListener(type, listener, options);
}
}
canvas.removeEventListener = function (type, listener) {
// console.log('canvas.removeEventListener', type);
if (my.isIDE) {
$global.document.removeEventListener(type, listener);
}
}
canvas.dispatchEvent = function (event = {}) {
// console.log('canvas.dispatchEvent' , event.type, event);
if (my.isIDE) {
$global.document.dispatchEvent(event);
}
}
Object.defineProperty(canvas, 'clientWidth', {
enumerable: true,
get: function get() {
return innerWidth
}
})
Object.defineProperty(canvas, 'clientHeight', {
enumerable: true,
get: function get() {
return innerHeight
}
})
return canvas
},
});
// Expose the global canvas
$global.screencanvas = $global.screencanvas || new CanvasProxy();
// NOTE: this is a hack operation
// let canvas = new window.Canvas()
// console.error(canvas instanceof window.Canvas) => false
export default CanvasProxy;

View File

@@ -0,0 +1,10 @@
import Node from './Node'
export default class Element extends Node {
className = ''
children = []
constructor() {
super()
}
}

View File

@@ -0,0 +1,14 @@
import {noop} from './util'
export default class Event {
cancelBubble = false
cancelable = false
target = null
timestampe = Date.now()
preventDefault = noop
stopPropagation = noop
constructor(type) {
this.type = type
}
}

View File

@@ -0,0 +1,57 @@
const _events = new WeakMap()
export default class EventTarget {
constructor() {
_events.set(this, {})
}
addEventListener(type, listener, options = {}) {
let events = _events.get(this)
if (!events) {
events = {}
_events.set(this, events)
}
if (!events[type]) {
events[type] = []
}
events[type].push(listener)
if (options.capture) {
// console.warn('EventTarget.addEventListener: options.capture is not implemented.')
}
if (options.once) {
// console.warn('EventTarget.addEventListener: options.once is not implemented.')
}
if (options.passive) {
// console.warn('EventTarget.addEventListener: options.passive is not implemented.')
}
}
removeEventListener(type, listener) {
const events = _events.get(this)
if (events) {
const listeners = events[type]
if (listeners && listeners.length > 0) {
for (let i = listeners.length; i--; i > 0) {
if (listeners[i] === listener) {
listeners.splice(i, 1)
break
}
}
}
}
}
dispatchEvent(event = {}) {
const listeners = _events.get(this)[event.type]
if (listeners) {
for (let i = 0; i < listeners.length; i++) {
listeners[i](event)
}
}
}
}

View File

@@ -0,0 +1,7 @@
import HTMLElement from './HTMLElement'
export default class HTMLAudioElement extends HTMLElement {
constructor() {
super('audio')
}
}

View File

@@ -0,0 +1,4 @@
import "./Canvas"
let HTMLCanvasElement = $global.screencanvas.constructor;
export default HTMLCanvasElement;

View File

@@ -0,0 +1,54 @@
import Element from './Element'
import { noop } from './util/index.js'
import { innerWidth, innerHeight } from './WindowProperties'
export default class HTMLElement extends Element {
className = ''
childern = []
style = {
width: `${innerWidth}px`,
height: `${innerHeight}px`
}
insertBefore = noop
innerHTML = ''
constructor(tagName = '') {
super()
this.tagName = tagName.toUpperCase()
}
setAttribute(name, value) {
this[name] = value
}
getAttribute(name) {
return this[name]
}
get clientWidth() {
const ret = parseInt(this.style.fontSize, 10) * this.innerHTML.length
return Number.isNaN(ret) ? 0 : ret
}
get clientHeight() {
const ret = parseInt(this.style.fontSize, 10)
return Number.isNaN(ret) ? 0 : ret
}
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: innerWidth,
height: innerHeight
}
}
focus() {
}
}

View File

@@ -0,0 +1,4 @@
var screencanvas = $global.screencanvas;
let HTMLImageElement = screencanvas.createImage().constructor;
export default HTMLImageElement;

View File

@@ -0,0 +1,7 @@
import './Canvas'
function Image () {
return $global.screencanvas.createImage();
}
export default Image;

View File

@@ -0,0 +1,5 @@
export default class ImageBitmap {
constructor() {
// TODO
}
}

View File

@@ -0,0 +1,34 @@
import EventTarget from './EventTarget.js'
export default class Node extends EventTarget {
constructor() {
super()
}
childNodes = []
appendChild(node) {
this.childNodes.push(node)
// if (node instanceof Node) {
// this.childNodes.push(node)
// } else {
// throw new TypeError('Failed to executed \'appendChild\' on \'Node\': parameter 1 is not of type \'Node\'.')
// }
}
cloneNode() {
const copyNode = Object.create(this)
Object.assign(copyNode, this)
return copyNode
}
removeChild(node) {
const index = this.childNodes.findIndex((child) => child === node)
if (index > -1) {
return this.childNodes.splice(index, 1)
}
return null
}
}

View File

@@ -0,0 +1,5 @@
export default class WebGLRenderingContext {
constructor() {
// TODO
}
}

View File

@@ -0,0 +1,139 @@
const _utils = require('../../../../common/utils');
const MAX_AMOUNT_WEBSOCKET = 1 // The maximum number of WEBSOCKET
let CURR_AMOUNT_WEBSOCKET = 0 // The current number of WEBSOCKET
export default class WebSocket {
static CONNECTING = 0 // The connection is not yet open.
static OPEN = 1 // The connection is open and ready to communicate.
static CLOSING = 2 // The connection is in the process of closing.
static CLOSED = 3 // The connection is closed or couldn't be opened.
binaryType = '' // TODO 更新 binaryType
bufferedAmount = 0 // TODO 更新 bufferedAmount
extensions = ''
onopen = null
onmessage = null
onerror = null
onclose = null
_onMessage = null
_onOpen = null
_onError = null
_onClose = null
_isReduced = false
protocol = '' // TODO 小程序内目前获取不到,实际上需要根据服务器选择的 sub-protocol 返回
readyState = 3
constructor(url, protocols = []) {
if(this._isMaxCount()){
console.warn(`Failed to construct 'WebSocket': Only ${CURR_AMOUNT_WEBSOCKET} WebSocket can be created at the same time on TaoBao.`);
return this;
}
if (typeof url !== 'string' || !(/(^ws:\/\/)|(^wss:\/\/)/).test(url)) {
throw new TypeError(`Failed to construct 'WebSocket': The URL '${url}' is invalid`)
}
this.url = url
this.readyState = WebSocket.CONNECTING
this._increaseCount();
my.connectSocket({
url,
fail: function fail(res) {
this._triggerEvent('error', res)
}
})
this._onOpen = (res) => {
this.readyState = WebSocket.OPEN
this._triggerEvent('open')
}
my.onSocketOpen(this._onOpen)
this._onMessage = (res) => {
if (res && res.data && res.isBuffer) {
res.data = _utils.base64ToArrayBuffer(res.data)
}
this._triggerEvent('message', res)
}
my.onSocketMessage(this._onMessage)
this._onError = (res) => {
this._triggerEvent('error', res)
this._decreaseCount();
}
my.onSocketError(this._onError)
this._onClose = (res) => {
this.readyState = WebSocket.CLOSED
this._triggerEvent('close')
this._removeAllSocketListenr()
this._decreaseCount();
}
my.onSocketClose(this._onClose)
return this
}
close() {
this.readyState = WebSocket.CLOSING
my.closeSocket()
}
send(data) {
if (typeof data !== 'string' && !(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
throw new TypeError(`Failed to send message: The data ${data} is invalid`)
}else{
var isBuffer = false;
if (data instanceof ArrayBuffer) {
data = _utils.arrayBufferToBase64(data)
isBuffer = true
}
my.sendSocketMessage({
data,
isBuffer,
fail: function (res) {
this._triggerEvent('error', res)
}
});
}
}
_triggerEvent(type, ...args) {
if (typeof this[`on${type}`] === 'function') {
this[`on${type}`].apply(this, args)
}
}
_removeAllSocketListenr(){
my.offSocketOpen(this._onOpen)
my.offSocketMessage(this._onMessage)
my.offSocketError(this._onError)
my.offSocketClose(this._onClose)
this._onOpen = null
this._onMessage = null
this._onError = null
this._onClose = null
}
_increaseCount(){
CURR_AMOUNT_WEBSOCKET += 1
}
_decreaseCount(){
if(!this._isReduced){
CURR_AMOUNT_WEBSOCKET -= 1
this._isReduced = true
}
}
_isMaxCount(){
return CURR_AMOUNT_WEBSOCKET >= MAX_AMOUNT_WEBSOCKET
}
}

View File

@@ -0,0 +1,27 @@
const { pixelRatio, windowWidth, windowHeight } = my.getSystemInfoSync()
const devicePixelRatio = pixelRatio;
let width, height;
{
width = windowWidth;
height = windowHeight;
}
export const innerWidth = width;
export const innerHeight = height;
export { devicePixelRatio }
export const screen = {
width,
height,
availWidth: innerWidth,
availHeight: innerHeight,
availLeft: 0,
availTop: 0,
}
export const performance = {
now: Date.now
};
export const ontouchstart = null;
export const ontouchmove = null;
export const ontouchend = null;

View File

@@ -0,0 +1,136 @@
import EventTarget from './EventTarget.js'
const _url = new WeakMap()
const _method = new WeakMap()
const _requestHeader = new WeakMap()
const _responseHeader = new WeakMap()
const _requestTask = new WeakMap()
function _triggerEvent(type, ...args) {
if (typeof this[`on${type}`] === 'function') {
this[`on${type}`].apply(this, args)
}
}
function _changeReadyState(readyState) {
this.readyState = readyState
_triggerEvent.call(this, 'readystatechange')
}
export default class XMLHttpRequest extends EventTarget {
// TODO 没法模拟 HEADERS_RECEIVED 和 LOADING 两个状态
static UNSEND = 0
static OPENED = 1
static HEADERS_RECEIVED = 2
static LOADING = 3
static DONE = 4
timeout = 0;
/*
* TODO 这一批事件应该是在 XMLHttpRequestEventTarget.prototype 上面的
*/
onabort = null
onerror = null
onload = null
onloadstart = null
onprogress = null
ontimeout = null
onloadend = null
onreadystatechange = null
readyState = 0
response = null
responseText = null
responseType = ''
responseXML = null
status = 0
statusText = ''
upload = {}
withCredentials = false
constructor() {
super();
_requestHeader.set(this, {
'content-type': 'application/json'
})
_responseHeader.set(this, {})
}
abort() {
const myRequestTask = _requestTask.get(this)
if (myRequestTask) {
myRequestTask.abort()
}
}
getAllResponseHeaders() {
const responseHeader = _responseHeader.get(this)
return Object.keys(responseHeader).map((header) => {
return `${header}: ${responseHeader[header]}`
}).join('\n')
}
getResponseHeader(header) {
return _responseHeader.get(this)[header]
}
open(method, url/* async, user, password 这几个参数在小程序内不支持*/) {
_method.set(this, method)
_url.set(this, url)
_changeReadyState.call(this, XMLHttpRequest.OPENED)
}
overrideMimeType() {
}
send(data = '') {
if (this.readyState !== XMLHttpRequest.OPENED) {
throw new Error("Failed to execute 'send' on 'XMLHttpRequest': The object's state must be OPENED.")
} else {
let myRequestTask = my.tb.request({
body: JSON.stringify(data),
url: _url.get(this),
method: _method.get(this),
headers: _requestHeader.get(this),
options: {timeout: this.timeout, enableSystemParams: true},
success: (res) => {
this.status = res.code;
this.responseText = this.response = res.content;
_responseHeader.set(this, _requestHeader.get(this))
_triggerEvent.call(this, 'loadstart')
_changeReadyState.call(this, XMLHttpRequest.HEADERS_RECEIVED)
_changeReadyState.call(this, XMLHttpRequest.LOADING)
_changeReadyState.call(this, XMLHttpRequest.DONE)
_triggerEvent.call(this, 'load')
_triggerEvent.call(this, 'loadend')
},
fail: (res) => {
_triggerEvent.call(this, 'error', res)
_triggerEvent.call(this, 'loadend')
}
})
_requestTask.set(this, myRequestTask);
}
}
setRequestHeader(header, value) {
const myHeader = _requestHeader.get(this)
myHeader[header] = value
_requestHeader.set(this, myHeader)
}
addEventListener(type, listener) {
if (typeof listener === 'function') {
let _this = this
let event = { target: _this }
this['on' + type] = function (event) {
listener.call(_this, event)
}
}
}
}

View File

@@ -0,0 +1,4 @@
let screencanvas = $global.screencanvas;
let cancelAnimationFrame = screencanvas.cancelAnimationFrame.bind(screencanvas);
export default cancelAnimationFrame;

View File

@@ -0,0 +1,124 @@
import * as _window from './window'
import HTMLElement from './HTMLElement'
import Image from './Image'
import * as Canvas from './Canvas'
import Audio from './Audio'
const events = {}
var document = {
readyState: 'complete',
visibilityState: 'visible',
documentElement: _window,
hidden: false,
style: {},
location: _window.location,
ontouchstart: null,
ontouchmove: null,
ontouchend: null,
head: new HTMLElement('head'),
body: new HTMLElement('body'),
createElement(tagName) {
tagName = tagName.toLowerCase();
if (tagName === 'canvas') {
return new Canvas()
} else if (tagName === 'audio') {
return new Audio()
} else if (tagName === 'img') {
return new Image()
}
return new HTMLElement(tagName)
},
createElementNS(nameSpace, tagName) {
return this.createElement(tagName);
},
getElementById(id) {
if (id === _window.canvas.id) {
return _window.canvas
}
return null
},
getElementsByTagName(tagName) {
if (tagName === 'head') {
return [document.head]
} else if (tagName === 'body') {
return [document.body]
} else if (tagName === 'canvas') {
return [_window.canvas]
}
return []
},
getElementsByName(tagName) {
if (tagName === 'head') {
return [document.head]
} else if (tagName === 'body') {
return [document.body]
} else if (tagName === 'canvas') {
return [_window.canvas]
}
return []
},
querySelector(query) {
if (query === 'head') {
return document.head
} else if (query === 'body') {
return document.body
} else if (query === 'canvas') {
return _window.canvas
} else if (query === `#${_window.canvas.id}`) {
return _window.canvas
}
return null
},
querySelectorAll(query) {
if (query === 'head') {
return [document.head]
} else if (query === 'body') {
return [document.body]
} else if (query === 'canvas') {
return [_window.canvas]
}
return []
},
addEventListener(type, listener) {
if (!events[type]) {
events[type] = []
}
events[type].push(listener)
},
removeEventListener(type, listener) {
const listeners = events[type]
if (listeners && listeners.length > 0) {
for (let i = listeners.length; i--; i > 0) {
if (listeners[i] === listener) {
listeners.splice(i, 1)
break
}
}
}
},
dispatchEvent(event) {
const listeners = events[event.type]
if (listeners) {
for (let i = 0; i < listeners.length; i++) {
listeners[i](event)
}
}
}
}
export default document

View File

@@ -0,0 +1,61 @@
import * as _window from './window'
import * as _document from './document'
var global = $global;
function inject () {
// 暴露全局的 canvas
_window.canvas = $global.screencanvas;
_window.document = _document;
_window.addEventListener = (type, listener) => {
_window.document.addEventListener(type, listener)
}
_window.removeEventListener = (type, listener) => {
_window.document.removeEventListener(type, listener)
}
_window.dispatchEvent = _window.document.dispatchEvent;
const { platform } = my.getSystemInfoSync()
// 开发者工具无法重定义 window
if (typeof __devtoolssubcontext === 'undefined' && platform === 'devtools') {
for (const key in _window) {
const descriptor = Object.getOwnPropertyDescriptor(global, key)
if (!descriptor || descriptor.configurable === true) {
Object.defineProperty(window, key, {
value: _window[key]
})
}
}
for (const key in _window.document) {
const descriptor = Object.getOwnPropertyDescriptor(global.document, key)
if (!descriptor || descriptor.configurable === true) {
Object.defineProperty(global.document, key, {
value: _window.document[key]
})
}
}
window.parent = window
} else {
for (const key in _window) {
global[key] = _window[key]
}
global.window = _window
window = global
window.top = window.parent = window
}
global.setTimeout = setTimeout;
global.clearTimeout = clearTimeout;
global.setInterval = setInterval;
global.clearInterval = clearInterval;
}
if (!global.__isAdapterInjected) {
global.__isAdapterInjected = true
inject()
}

View File

@@ -0,0 +1,37 @@
const localStorage = {
get length() {
const { keys } = my.getStorageInfoSync()
return keys.length
},
key(n) {
const { keys } = my.getStorageInfoSync()
return keys[n]
},
getItem(key) {
let ret = my.getStorageSync({
key,
});
return ret && ret.data;
},
setItem(key, data) {
return my.setStorageSync({
key,
data,
});
},
removeItem(key) {
my.removeStorageSync(key)
},
clear() {
my.clearStorageSync()
}
}
export default localStorage

View File

@@ -0,0 +1,7 @@
var location = {
href: 'game.js',
reload() {
}
}
export default location

View File

@@ -0,0 +1,38 @@
import { noop } from './util/index.js'
// TODO 需要 my.getSystemInfo 获取更详细信息
const systemInfo = my.getSystemInfoSync()
console.log(systemInfo)
const system = systemInfo.system;
const platform = systemInfo.platform;
const language = systemInfo.language;
const version = systemInfo.version;
const android = system ? system.toLowerCase().indexOf('android') !== -1 : false;
const uaDesc = android ? `Android; CPU ${system}` : `iPhone; CPU iPhone OS ${system} like Mac OS X`;
const ua = `Mozilla/5.0 (${uaDesc}) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301 MicroMessenger/${version} MiniGame NetType/WIFI Language/${language}`;
const navigator = {
platform,
language: language,
appVersion: `5.0 (${uaDesc}) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1`,
userAgent: ua,
onLine: true, // TODO 用 my.getNetworkStateChange 和 my.onNetworkStateChange 来返回真实的状态
// TODO 用 my.getLocation 来封装 geolocation
geolocation: {
getCurrentPosition: noop,
watchPosition: noop,
clearWatch: noop
}
}
if (my.onNetworkStatusChange) {
my.onNetworkStatusChange(function(event){
navigator.onLine = event.isConnected;
});
}
export default navigator

View File

@@ -0,0 +1,4 @@
let screencanvas = $global.screencanvas;
let requestAnimationFrame = screencanvas.requestAnimationFrame.bind(screencanvas);
export default requestAnimationFrame;

View File

@@ -0,0 +1 @@
export function noop() {}

View File

@@ -0,0 +1,15 @@
export navigator from './navigator'
export XMLHttpRequest from './XMLHttpRequest'
export WebSocket from './WebSocket'
export Image from './Image'
export ImageBitmap from './ImageBitmap'
export HTMLElement from './HTMLElement'
export HTMLCanvasElement from './HTMLCanvasElement'
export HTMLImageElement from './HTMLImageElement'
export WebGLRenderingContext from './WebGLRenderingContext'
export localStorage from './localStorage'
export location from './location'
export requestAnimationFrame from './requestAnimationFrame'
export cancelAnimationFrame from './cancelAnimationFrame'
export * from './WindowProperties'