415gotit
This commit is contained in:
@@ -1,484 +1,239 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Window Mock
|
||||
*
|
||||
* The global object for browser environments.
|
||||
* This ties everything together.
|
||||
* 总装:window 沙盒
|
||||
* 按 P0→P1→P2 顺序挂载所有 mock,并用 Proxy 屏蔽 bot 字段
|
||||
*/
|
||||
|
||||
import windowStubs from '../stubs/window_stubs.json' with { type: 'json' };
|
||||
import chromeProps from '../stubs/chrome_props.json' with { type: 'json' };
|
||||
import { createScreen } from './screen.js';
|
||||
import { createNavigator } from './navigator.js';
|
||||
import { createDocument } from './document.js';
|
||||
import { createPerformance } from './performance.js';
|
||||
import { createCrypto } from './crypto.js';
|
||||
import { createStorage } from './storage.js';
|
||||
const { createNative, nativeClass } = require('./native');
|
||||
const { isBotKey } = require('./bot_shield');
|
||||
const performanceMock = require('./performance');
|
||||
const navigatorMock = require('./navigator');
|
||||
const { RTCPeerConnection, OfflineAudioContext } = require('./webapi');
|
||||
const { HTMLCanvasElement, CanvasRenderingContext2D } = require('./canvas');
|
||||
const { cryptoMock, Storage, IDBFactory, Notification, atob, btoa } = require('./crypto');
|
||||
const screenMock = require('./screen');
|
||||
const HTMLDocument = require('./document');
|
||||
|
||||
export function createWindow(fingerprint = {}) {
|
||||
const stubs = { ...windowStubs, ...fingerprint.window };
|
||||
// ── 基础 window 对象 ─────────────────────────────────────────
|
||||
const _win = {
|
||||
|
||||
const screen = createScreen(fingerprint.screen);
|
||||
const navigator = createNavigator(fingerprint.navigator);
|
||||
const document = createDocument(fingerprint);
|
||||
const performance = createPerformance(fingerprint);
|
||||
const crypto = createCrypto();
|
||||
const localStorage = createStorage();
|
||||
const sessionStorage = createStorage();
|
||||
// ── P0: 核心 API ──────────────────────────────────────
|
||||
performance: performanceMock,
|
||||
navigator: navigatorMock,
|
||||
screen: screenMock,
|
||||
crypto: cryptoMock,
|
||||
|
||||
const eventListeners = new Map();
|
||||
let origin = fingerprint.origin || 'https://example.com';
|
||||
RTCPeerConnection,
|
||||
webkitRTCPeerConnection: RTCPeerConnection,
|
||||
OfflineAudioContext,
|
||||
|
||||
const location = createLocation(fingerprint.url || 'https://example.com/');
|
||||
// ── P1: Canvas ────────────────────────────────────────
|
||||
HTMLCanvasElement,
|
||||
CanvasRenderingContext2D,
|
||||
|
||||
const history = {
|
||||
length: 1,
|
||||
// ── P1: Storage / IDB ─────────────────────────────────
|
||||
localStorage: new Storage(),
|
||||
sessionStorage: new Storage(),
|
||||
indexedDB: new IDBFactory(),
|
||||
IDBFactory,
|
||||
|
||||
// ── P1: Notification ──────────────────────────────────
|
||||
Notification,
|
||||
|
||||
// ── P1: atob / btoa ───────────────────────────────────
|
||||
atob,
|
||||
btoa,
|
||||
|
||||
// ── P1: Document ──────────────────────────────────────
|
||||
document: new HTMLDocument(),
|
||||
HTMLDocument,
|
||||
|
||||
// ── P2: 移动端触摸 → 桌面不存在 ──────────────────────
|
||||
// ontouchstart: 不定义,Proxy 返回 undefined
|
||||
|
||||
// ── 基础 JS 全局 ─────────────────────────────────────
|
||||
Promise,
|
||||
Object,
|
||||
Array,
|
||||
Function,
|
||||
Number,
|
||||
String,
|
||||
Boolean,
|
||||
Symbol,
|
||||
Date,
|
||||
RegExp,
|
||||
Error,
|
||||
Math,
|
||||
JSON,
|
||||
parseInt,
|
||||
parseFloat,
|
||||
isNaN,
|
||||
isFinite,
|
||||
decodeURI,
|
||||
decodeURIComponent,
|
||||
encodeURI,
|
||||
encodeURIComponent,
|
||||
escape,
|
||||
unescape,
|
||||
eval,
|
||||
undefined,
|
||||
Infinity,
|
||||
NaN,
|
||||
globalThis: null, // 在 Proxy 建好后回填
|
||||
|
||||
// ── 定时器(Node 原生) ───────────────────────────────
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
queueMicrotask,
|
||||
|
||||
// ── 其他常见 window 属性 ──────────────────────────────
|
||||
location: {
|
||||
href: 'https://newassets.hcaptcha.com/captcha/v1/xxx/static/hcaptcha.html',
|
||||
origin: 'https://newassets.hcaptcha.com',
|
||||
protocol: 'https:',
|
||||
host: 'newassets.hcaptcha.com',
|
||||
hostname: 'newassets.hcaptcha.com',
|
||||
port: '',
|
||||
pathname: '/captcha/v1/xxx/static/hcaptcha.html',
|
||||
search: '',
|
||||
hash: '',
|
||||
ancestorOrigins: { 0: 'https://b.stripecdn.com', 1: 'https://js.stripe.com', length: 2 },
|
||||
},
|
||||
|
||||
innerWidth: 530,
|
||||
innerHeight: 915,
|
||||
outerWidth: 530,
|
||||
outerHeight: 915,
|
||||
devicePixelRatio: 2,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
screenLeft: 0,
|
||||
screenTop: 0,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
pageXOffset: 0,
|
||||
pageYOffset: 0,
|
||||
|
||||
closed: false,
|
||||
name: '',
|
||||
status: '',
|
||||
opener: null,
|
||||
parent: null, // 回填
|
||||
top: null, // 回填
|
||||
self: null, // 回填
|
||||
frames: null, // 回填
|
||||
length: 0,
|
||||
isSecureContext: true,
|
||||
crossOriginIsolated: false,
|
||||
originAgentCluster: false,
|
||||
|
||||
history: {
|
||||
length: 1,
|
||||
state: null,
|
||||
scrollRestoration: 'auto',
|
||||
state: null,
|
||||
back() {},
|
||||
forward() {},
|
||||
go() {},
|
||||
pushState() {},
|
||||
replaceState() {},
|
||||
};
|
||||
go: createNative('go', function () {}),
|
||||
back: createNative('back', function () {}),
|
||||
forward: createNative('forward', function () {}),
|
||||
pushState: createNative('pushState', function () {}),
|
||||
replaceState: createNative('replaceState', function () {}),
|
||||
},
|
||||
|
||||
const win = {
|
||||
// Window identity
|
||||
window: null, // Self-reference, set below
|
||||
self: null,
|
||||
top: null,
|
||||
parent: null,
|
||||
globalThis: null,
|
||||
frames: [],
|
||||
length: 0,
|
||||
frameElement: null,
|
||||
opener: null,
|
||||
closed: false,
|
||||
name: '',
|
||||
fetch: createNative('fetch', function (url, opts) {
|
||||
// 沙盒里一般不真正发请求,返回 resolved 空 response
|
||||
return Promise.resolve({
|
||||
ok: true, status: 200,
|
||||
json: () => Promise.resolve({}),
|
||||
text: () => Promise.resolve(''),
|
||||
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
||||
});
|
||||
}),
|
||||
|
||||
// Core objects
|
||||
document,
|
||||
navigator,
|
||||
screen,
|
||||
location,
|
||||
history,
|
||||
performance,
|
||||
crypto,
|
||||
localStorage,
|
||||
sessionStorage,
|
||||
Request: createNative('Request', function (url, opts) { this.url = url; this.method = opts?.method || 'GET'; }),
|
||||
Response: createNative('Response', function (body, opts) { this.status = opts?.status || 200; }),
|
||||
Headers: createNative('Headers', function () { this._h = {}; }),
|
||||
|
||||
// Visual viewport
|
||||
visualViewport: {
|
||||
width: stubs.innerWidth,
|
||||
height: stubs.innerHeight,
|
||||
offsetLeft: 0,
|
||||
offsetTop: 0,
|
||||
pageLeft: 0,
|
||||
pageTop: 0,
|
||||
scale: 1,
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
},
|
||||
URL: createNative('URL', function (url, base) {
|
||||
const u = new (require('url').URL)(url, base);
|
||||
Object.assign(this, u);
|
||||
}),
|
||||
URLSearchParams,
|
||||
|
||||
// Dimensions
|
||||
innerWidth: stubs.innerWidth,
|
||||
innerHeight: stubs.innerHeight,
|
||||
outerWidth: stubs.outerWidth,
|
||||
outerHeight: stubs.outerHeight,
|
||||
devicePixelRatio: stubs.devicePixelRatio,
|
||||
addEventListener: createNative('addEventListener', function () {}),
|
||||
removeEventListener: createNative('removeEventListener', function () {}),
|
||||
dispatchEvent: createNative('dispatchEvent', function () { return true; }),
|
||||
postMessage: createNative('postMessage', function () {}),
|
||||
|
||||
// Scroll
|
||||
pageXOffset: stubs.pageXOffset,
|
||||
pageYOffset: stubs.pageYOffset,
|
||||
scrollX: stubs.scrollX,
|
||||
scrollY: stubs.scrollY,
|
||||
alert: createNative('alert', function () {}),
|
||||
confirm: createNative('confirm', function () { return false; }),
|
||||
prompt: createNative('prompt', function () { return null; }),
|
||||
|
||||
// Screen position
|
||||
screenX: stubs.screenX,
|
||||
screenY: stubs.screenY,
|
||||
screenLeft: stubs.screenLeft,
|
||||
screenTop: stubs.screenTop,
|
||||
requestAnimationFrame: createNative('requestAnimationFrame', function (cb) { return setTimeout(cb, 16); }),
|
||||
cancelAnimationFrame: createNative('cancelAnimationFrame', function (id) { clearTimeout(id); }),
|
||||
requestIdleCallback: createNative('requestIdleCallback', function (cb) { return setTimeout(() => cb({ timeRemaining: () => 50, didTimeout: false }), 1); }),
|
||||
cancelIdleCallback: createNative('cancelIdleCallback', function (id) { clearTimeout(id); }),
|
||||
|
||||
// Security
|
||||
origin,
|
||||
isSecureContext: stubs.isSecureContext,
|
||||
crossOriginIsolated: stubs.crossOriginIsolated,
|
||||
originAgentCluster: stubs.originAgentCluster,
|
||||
getComputedStyle: createNative('getComputedStyle', function () {
|
||||
return new Proxy({}, { get: (_, p) => p === 'getPropertyValue' ? (() => '') : '' });
|
||||
}),
|
||||
|
||||
// Chrome object
|
||||
chrome: chromeProps,
|
||||
structuredClone: createNative('structuredClone', (v) => JSON.parse(JSON.stringify(v))),
|
||||
|
||||
// Caches
|
||||
caches: {
|
||||
open: () => Promise.resolve({
|
||||
match: () => Promise.resolve(undefined),
|
||||
matchAll: () => Promise.resolve([]),
|
||||
add: () => Promise.resolve(),
|
||||
addAll: () => Promise.resolve(),
|
||||
put: () => Promise.resolve(),
|
||||
delete: () => Promise.resolve(false),
|
||||
keys: () => Promise.resolve([]),
|
||||
}),
|
||||
match: () => Promise.resolve(undefined),
|
||||
has: () => Promise.resolve(false),
|
||||
delete: () => Promise.resolve(false),
|
||||
keys: () => Promise.resolve([]),
|
||||
},
|
||||
TextEncoder,
|
||||
TextDecoder,
|
||||
Uint8Array,
|
||||
Int8Array,
|
||||
Uint16Array,
|
||||
Int16Array,
|
||||
Uint32Array,
|
||||
Int32Array,
|
||||
Uint8ClampedArray,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
ArrayBuffer,
|
||||
DataView,
|
||||
Map,
|
||||
Set,
|
||||
WeakMap,
|
||||
WeakSet,
|
||||
Proxy,
|
||||
Reflect,
|
||||
BigInt,
|
||||
Symbol,
|
||||
WebAssembly,
|
||||
};
|
||||
|
||||
// IndexedDB
|
||||
indexedDB: createIndexedDB(),
|
||||
// ── 建 Proxy:屏蔽 bot 字段 + 回填自引用 ────────────────────
|
||||
const windowProxy = new Proxy(_win, {
|
||||
get(target, prop) {
|
||||
if (isBotKey(prop)) return undefined; // 🚨 bot 字段全部返回 undefined
|
||||
const val = target[prop];
|
||||
if (val === null && ['self','window','frames','parent','top','globalThis'].includes(prop)) {
|
||||
return windowProxy;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
has(target, prop) {
|
||||
if (isBotKey(prop)) return false; // 拦截 'webdriver' in window
|
||||
return prop in target;
|
||||
},
|
||||
set(target, prop, val) {
|
||||
if (isBotKey(prop)) return true; // 静默丢弃 bot 字段的写入
|
||||
target[prop] = val;
|
||||
return true;
|
||||
},
|
||||
ownKeys(target) {
|
||||
return Reflect.ownKeys(target).filter(k => !isBotKey(k));
|
||||
},
|
||||
});
|
||||
|
||||
// Scheduler
|
||||
scheduler: {
|
||||
postTask: (cb) => Promise.resolve(cb()),
|
||||
},
|
||||
// 回填自引用
|
||||
_win.self = windowProxy;
|
||||
_win.window = windowProxy;
|
||||
_win.globalThis = windowProxy;
|
||||
_win.frames = windowProxy;
|
||||
_win.parent = windowProxy;
|
||||
_win.top = windowProxy;
|
||||
|
||||
// Speech
|
||||
speechSynthesis: {
|
||||
pending: false,
|
||||
speaking: false,
|
||||
paused: false,
|
||||
getVoices: () => [],
|
||||
speak: () => {},
|
||||
cancel: () => {},
|
||||
pause: () => {},
|
||||
resume: () => {},
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
},
|
||||
|
||||
// CSS
|
||||
CSS: {
|
||||
supports: () => true,
|
||||
escape: (str) => str,
|
||||
px: (n) => `${n}px`,
|
||||
em: (n) => `${n}em`,
|
||||
rem: (n) => `${n}rem`,
|
||||
vh: (n) => `${n}vh`,
|
||||
vw: (n) => `${n}vw`,
|
||||
percent: (n) => `${n}%`,
|
||||
},
|
||||
|
||||
// Match media
|
||||
matchMedia(query) {
|
||||
const matches = query.includes('prefers-color-scheme: light') ||
|
||||
query.includes('(min-width:') ||
|
||||
query.includes('screen');
|
||||
return {
|
||||
matches,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
addListener() {},
|
||||
removeListener() {},
|
||||
};
|
||||
},
|
||||
|
||||
// Computed style
|
||||
getComputedStyle(element, pseudo) {
|
||||
return new Proxy({}, {
|
||||
get(target, prop) {
|
||||
if (prop === 'getPropertyValue') return () => '';
|
||||
if (prop === 'length') return 0;
|
||||
if (prop === 'cssText') return '';
|
||||
return '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Scroll methods
|
||||
scroll() {},
|
||||
scrollTo() {},
|
||||
scrollBy() {},
|
||||
|
||||
// Focus
|
||||
focus() {},
|
||||
blur() {},
|
||||
|
||||
// Print
|
||||
print() {},
|
||||
|
||||
// Alerts
|
||||
alert() {},
|
||||
confirm() { return false; },
|
||||
prompt() { return null; },
|
||||
|
||||
// Open/Close
|
||||
open() { return null; },
|
||||
close() {},
|
||||
stop() {},
|
||||
|
||||
// Animation
|
||||
requestAnimationFrame(cb) {
|
||||
return setTimeout(() => cb(performance.now()), 16);
|
||||
},
|
||||
cancelAnimationFrame(id) {
|
||||
clearTimeout(id);
|
||||
},
|
||||
requestIdleCallback(cb) {
|
||||
return setTimeout(() => cb({
|
||||
didTimeout: false,
|
||||
timeRemaining: () => 50,
|
||||
}), 1);
|
||||
},
|
||||
cancelIdleCallback(id) {
|
||||
clearTimeout(id);
|
||||
},
|
||||
|
||||
// Timers
|
||||
setTimeout: globalThis.setTimeout,
|
||||
clearTimeout: globalThis.clearTimeout,
|
||||
setInterval: globalThis.setInterval,
|
||||
clearInterval: globalThis.clearInterval,
|
||||
queueMicrotask: globalThis.queueMicrotask,
|
||||
|
||||
// Encoding
|
||||
btoa(str) {
|
||||
return Buffer.from(str, 'binary').toString('base64');
|
||||
},
|
||||
atob(str) {
|
||||
return Buffer.from(str, 'base64').toString('binary');
|
||||
},
|
||||
|
||||
// Fetch API
|
||||
fetch: globalThis.fetch,
|
||||
Request: globalThis.Request,
|
||||
Response: globalThis.Response,
|
||||
Headers: globalThis.Headers,
|
||||
|
||||
// URL
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
|
||||
// Events
|
||||
Event: globalThis.Event || class Event {
|
||||
constructor(type, options = {}) {
|
||||
this.type = type;
|
||||
this.bubbles = options.bubbles || false;
|
||||
this.cancelable = options.cancelable || false;
|
||||
this.composed = options.composed || false;
|
||||
this.defaultPrevented = false;
|
||||
this.timeStamp = Date.now();
|
||||
}
|
||||
preventDefault() { this.defaultPrevented = true; }
|
||||
stopPropagation() {}
|
||||
stopImmediatePropagation() {}
|
||||
},
|
||||
CustomEvent: globalThis.CustomEvent || class CustomEvent extends Event {
|
||||
constructor(type, options = {}) {
|
||||
super(type, options);
|
||||
this.detail = options.detail || null;
|
||||
}
|
||||
},
|
||||
MessageEvent: class MessageEvent {
|
||||
constructor(type, options = {}) {
|
||||
this.type = type;
|
||||
this.data = options.data;
|
||||
this.origin = options.origin || '';
|
||||
this.lastEventId = options.lastEventId || '';
|
||||
this.source = options.source || null;
|
||||
this.ports = options.ports || [];
|
||||
}
|
||||
},
|
||||
|
||||
// Event listener management
|
||||
addEventListener(type, listener, options) {
|
||||
if (!eventListeners.has(type)) {
|
||||
eventListeners.set(type, []);
|
||||
}
|
||||
eventListeners.get(type).push(listener);
|
||||
},
|
||||
removeEventListener(type, listener, options) {
|
||||
const listeners = eventListeners.get(type);
|
||||
if (listeners) {
|
||||
const idx = listeners.indexOf(listener);
|
||||
if (idx > -1) listeners.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
dispatchEvent(event) {
|
||||
const listeners = eventListeners.get(event.type);
|
||||
if (listeners) {
|
||||
listeners.forEach(fn => fn(event));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// Post message
|
||||
postMessage(data, targetOrigin, transfer) {},
|
||||
|
||||
// Workers
|
||||
Worker: class Worker {
|
||||
constructor(url) {
|
||||
this.onmessage = null;
|
||||
this.onerror = null;
|
||||
}
|
||||
postMessage() {}
|
||||
terminate() {}
|
||||
addEventListener() {}
|
||||
removeEventListener() {}
|
||||
},
|
||||
SharedWorker: undefined,
|
||||
|
||||
// Blob & File
|
||||
Blob: globalThis.Blob,
|
||||
File: globalThis.File || class File extends Blob {
|
||||
constructor(bits, name, options = {}) {
|
||||
super(bits, options);
|
||||
this.name = name;
|
||||
this.lastModified = options.lastModified || Date.now();
|
||||
}
|
||||
},
|
||||
FileReader: class FileReader {
|
||||
readAsText() { this.onload?.({ target: { result: '' } }); }
|
||||
readAsDataURL() { this.onload?.({ target: { result: 'data:,' } }); }
|
||||
readAsArrayBuffer() { this.onload?.({ target: { result: new ArrayBuffer(0) } }); }
|
||||
readAsBinaryString() { this.onload?.({ target: { result: '' } }); }
|
||||
abort() {}
|
||||
},
|
||||
|
||||
// ArrayBuffer & TypedArrays
|
||||
ArrayBuffer: globalThis.ArrayBuffer,
|
||||
SharedArrayBuffer: globalThis.SharedArrayBuffer,
|
||||
Uint8Array: globalThis.Uint8Array,
|
||||
Uint16Array: globalThis.Uint16Array,
|
||||
Uint32Array: globalThis.Uint32Array,
|
||||
Int8Array: globalThis.Int8Array,
|
||||
Int16Array: globalThis.Int16Array,
|
||||
Int32Array: globalThis.Int32Array,
|
||||
Float32Array: globalThis.Float32Array,
|
||||
Float64Array: globalThis.Float64Array,
|
||||
Uint8ClampedArray: globalThis.Uint8ClampedArray,
|
||||
BigInt64Array: globalThis.BigInt64Array,
|
||||
BigUint64Array: globalThis.BigUint64Array,
|
||||
DataView: globalThis.DataView,
|
||||
|
||||
// Text encoding
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
|
||||
// Intl
|
||||
Intl: globalThis.Intl,
|
||||
|
||||
// WebAssembly
|
||||
WebAssembly: globalThis.WebAssembly,
|
||||
|
||||
// Core language
|
||||
Object: globalThis.Object,
|
||||
Array: globalThis.Array,
|
||||
String: globalThis.String,
|
||||
Number: globalThis.Number,
|
||||
Boolean: globalThis.Boolean,
|
||||
Symbol: globalThis.Symbol,
|
||||
BigInt: globalThis.BigInt,
|
||||
Math: globalThis.Math,
|
||||
Date: globalThis.Date,
|
||||
JSON: globalThis.JSON,
|
||||
RegExp: globalThis.RegExp,
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
SyntaxError: globalThis.SyntaxError,
|
||||
ReferenceError: globalThis.ReferenceError,
|
||||
EvalError: globalThis.EvalError,
|
||||
URIError: globalThis.URIError,
|
||||
AggregateError: globalThis.AggregateError,
|
||||
Promise: globalThis.Promise,
|
||||
Proxy: globalThis.Proxy,
|
||||
Reflect: globalThis.Reflect,
|
||||
Map: globalThis.Map,
|
||||
Set: globalThis.Set,
|
||||
WeakMap: globalThis.WeakMap,
|
||||
WeakSet: globalThis.WeakSet,
|
||||
WeakRef: globalThis.WeakRef,
|
||||
FinalizationRegistry: globalThis.FinalizationRegistry,
|
||||
|
||||
// Functions
|
||||
Function: globalThis.Function,
|
||||
eval: globalThis.eval,
|
||||
isNaN: globalThis.isNaN,
|
||||
isFinite: globalThis.isFinite,
|
||||
parseFloat: globalThis.parseFloat,
|
||||
parseInt: globalThis.parseInt,
|
||||
decodeURI: globalThis.decodeURI,
|
||||
decodeURIComponent: globalThis.decodeURIComponent,
|
||||
encodeURI: globalThis.encodeURI,
|
||||
encodeURIComponent: globalThis.encodeURIComponent,
|
||||
|
||||
// Console
|
||||
console: globalThis.console,
|
||||
|
||||
// Undefined/NaN/Infinity
|
||||
undefined: undefined,
|
||||
NaN: NaN,
|
||||
Infinity: Infinity,
|
||||
};
|
||||
|
||||
// Self-references
|
||||
win.window = win;
|
||||
win.self = win;
|
||||
win.top = win;
|
||||
win.parent = win;
|
||||
win.globalThis = win;
|
||||
|
||||
// Connect document to window
|
||||
document.defaultView = win;
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
function createLocation(url) {
|
||||
const parsed = new URL(url);
|
||||
|
||||
return {
|
||||
href: parsed.href,
|
||||
protocol: parsed.protocol,
|
||||
host: parsed.host,
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port,
|
||||
pathname: parsed.pathname,
|
||||
search: parsed.search,
|
||||
hash: parsed.hash,
|
||||
origin: parsed.origin,
|
||||
ancestorOrigins: {
|
||||
length: 0,
|
||||
item: () => null,
|
||||
contains: () => false,
|
||||
},
|
||||
assign() {},
|
||||
replace() {},
|
||||
reload() {},
|
||||
toString() { return this.href; },
|
||||
};
|
||||
}
|
||||
|
||||
function createIndexedDB() {
|
||||
return {
|
||||
open() {
|
||||
return {
|
||||
result: null,
|
||||
error: null,
|
||||
readyState: 'done',
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
onupgradeneeded: null,
|
||||
onblocked: null,
|
||||
};
|
||||
},
|
||||
deleteDatabase() {
|
||||
return {
|
||||
result: undefined,
|
||||
error: null,
|
||||
readyState: 'done',
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
onblocked: null,
|
||||
};
|
||||
},
|
||||
databases() {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
cmp() {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
}
|
||||
module.exports = windowProxy;
|
||||
|
||||
Reference in New Issue
Block a user