'use strict'; /** * P0: Performance mock * hsw 检测:timing / timeOrigin / getEntriesByType('resource') / getEntriesByType('navigation') * Now with: 5µs quantization, proper prototype chain (Performance -> EventTarget -> Object), * PerformanceEntry / PerformanceResourceTiming / PerformanceNavigationTiming classes, * getEntries() method */ const { nativeMethod: M } = require('./native'); const { Performance, PerformanceEntry, PerformanceResourceTiming, PerformanceNavigationTiming, } = require('./class_registry'); const NAV_START = Date.now() - 1200; const timingData = { navigationStart: NAV_START, fetchStart: NAV_START + 11, domainLookupStart: NAV_START + 11, domainLookupEnd: NAV_START + 11, connectStart: NAV_START + 11, secureConnectionStart: NAV_START + 11, connectEnd: NAV_START + 11, requestStart: NAV_START + 37, responseStart: NAV_START + 47, responseEnd: NAV_START + 114, domLoading: NAV_START + 203, domInteractive: NAV_START + 399, domContentLoadedEventStart: NAV_START + 399, domContentLoadedEventEnd: NAV_START + 399, domComplete: NAV_START + 399, loadEventStart: NAV_START + 399, loadEventEnd: NAV_START + 399, redirectStart: 0, redirectEnd: 0, unloadEventStart: 0, unloadEventEnd: 0, }; // ── Helper: create PerformanceResourceTiming instance ──────────── const makeResourceEntry = (data) => { const entry = Object.create(PerformanceResourceTiming.prototype); Object.assign(entry, data); return entry; }; // ── Helper: create PerformanceNavigationTiming instance ────────── const makeNavEntry = (data) => { const entry = Object.create(PerformanceNavigationTiming.prototype); Object.assign(entry, data); return entry; }; // 模拟 resource 条目(hsw 会查 checksiteconfig 请求痕迹) const resourceEntries = [ makeResourceEntry({ name: 'https://api.hcaptcha.com/checksiteconfig?v=xxx&host=b.stripecdn.com&sitekey=xxx&sc=1&swa=1&spst=1', entryType: 'resource', initiatorType: 'xmlhttprequest', startTime: 399.2, duration: 643.1, fetchStart: 399.2, responseEnd: 1042.3, transferSize: 0, encodedBodySize: 0, decodedBodySize: 0, responseStatus: 200, deliveryType: '', nextHopProtocol: '', contentEncoding: 'br', workerStart: 0, redirectStart: 0, redirectEnd: 0, domainLookupStart: 0, domainLookupEnd: 0, connectStart: 0, secureConnectionStart: 0, connectEnd: 0, requestStart: 0, responseStart: 0, firstInterimResponseStart: 0, finalResponseHeadersStart: 0, serverTiming: [], renderBlockingStatus: 'non-blocking', }), makeResourceEntry({ name: 'https://newassets.hcaptcha.com/c/xxx/hsw.js', entryType: 'resource', initiatorType: 'script', deliveryType: 'cache', nextHopProtocol: 'h2', startTime: 1043.8, duration: 5.7, fetchStart: 1043.8, domainLookupStart: 1043.8, domainLookupEnd: 1043.8, connectStart: 1043.8, secureConnectionStart: 1043.8, connectEnd: 1043.8, requestStart: 1044.6, responseStart: 1044.6, firstInterimResponseStart: 1044.6, finalResponseHeadersStart: 0, responseEnd: 1049.5, transferSize: 0, encodedBodySize: 359059, decodedBodySize: 829689, responseStatus: 200, contentEncoding: 'gzip', workerStart: 0, redirectStart: 0, redirectEnd: 0, serverTiming: [], renderBlockingStatus: 'non-blocking', }), ]; // 模拟 navigation 条目 const navigationEntry = makeNavEntry({ name: 'https://newassets.hcaptcha.com/captcha/v1/xxx/static/hcaptcha.html', entryType: 'navigation', initiatorType: 'navigation', deliveryType: 'cache', nextHopProtocol: 'h2', startTime: 0, duration: 399.9, fetchStart: 11.6, domainLookupStart: 11.6, domainLookupEnd: 11.6, connectStart: 11.6, secureConnectionStart: 11.6, connectEnd: 11.6, requestStart: 37.6, responseStart: 47.4, firstInterimResponseStart: 47.4, finalResponseHeadersStart: 0, responseEnd: 114.2, transferSize: 0, encodedBodySize: 167487, decodedBodySize: 567885, responseStatus: 200, redirectStart: 0, redirectEnd: 0, unloadEventStart: 0, unloadEventEnd: 0, domInteractive: 399.4, domContentLoadedEventStart: 399.5, domContentLoadedEventEnd: 399.5, domComplete: 399.8, loadEventStart: 399.9, loadEventEnd: 399.9, type: 'navigate', redirectCount: 0, activationStart: 0, criticalCHRestart: 0, notRestoredReasons: null, confidence: null, serverTiming: [], workerStart: 0, contentEncoding: 'br', renderBlockingStatus: 'non-blocking', }); // ── Build performance object with proper prototype ────────────── const performanceMock = Object.create(Performance.prototype); Object.assign(performanceMock, { timeOrigin: NAV_START, timing: timingData, navigation: { type: 0, redirectCount: 0 }, // 5µs quantization (Chromium feature) now: M('now', 0, () => { const raw = Date.now() - NAV_START; return Math.round(raw * 200) / 200; // 0.005ms = 5µs steps }), getEntries: M('getEntries', 0, () => { return [navigationEntry, ...resourceEntries]; }), getEntriesByType: M('getEntriesByType', 1, (type) => { if (type === 'resource') return resourceEntries; if (type === 'navigation') return [navigationEntry]; if (type === 'paint') return []; if (type === 'mark') return []; if (type === 'measure') return []; return []; }), getEntriesByName: M('getEntriesByName', 1, (name) => { return [...resourceEntries, navigationEntry].filter(e => e.name === name); }), mark: M('mark', 1, () => {}), measure: M('measure', 1, () => {}), clearMarks: M('clearMarks', 0, () => {}), clearMeasures: M('clearMeasures', 0, () => {}), clearResourceTimings: M('clearResourceTimings', 0, () => {}), setResourceTimingBufferSize: M('setResourceTimingBufferSize', 1, () => {}), addEventListener: M('addEventListener', 2, () => {}), removeEventListener: M('removeEventListener', 2, () => {}), }); module.exports = performanceMock;