160 lines
6.1 KiB
JavaScript
160 lines
6.1 KiB
JavaScript
const { scaffoldModule } = require("./scaffold-module");
|
|
|
|
function toFiniteNumber(value, fallback) {
|
|
return Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
function cloneRecord(value) {
|
|
return Object.assign({}, value || {});
|
|
}
|
|
|
|
function normalizePerformanceSeed(seed = {}) {
|
|
const performanceSeed = seed.performance && typeof seed.performance === "object" ? seed.performance : seed;
|
|
const navigationStart = toFiniteNumber(performanceSeed.navigationStart, Date.now());
|
|
const timeOrigin = toFiniteNumber(performanceSeed.timeOrigin, navigationStart);
|
|
const nowOffset = toFiniteNumber(performanceSeed.nowOffset, 12.345);
|
|
const timing = cloneRecord(performanceSeed.timing);
|
|
|
|
if (!Number.isFinite(timing.navigationStart)) timing.navigationStart = navigationStart;
|
|
if (!Number.isFinite(timing.fetchStart)) timing.fetchStart = timing.navigationStart;
|
|
if (!Number.isFinite(timing.domainLookupStart)) timing.domainLookupStart = timing.fetchStart + 1;
|
|
if (!Number.isFinite(timing.domainLookupEnd)) timing.domainLookupEnd = timing.domainLookupStart + 1;
|
|
if (!Number.isFinite(timing.connectStart)) timing.connectStart = timing.domainLookupEnd;
|
|
if (!Number.isFinite(timing.connectEnd)) timing.connectEnd = timing.connectStart + 2;
|
|
if (!Number.isFinite(timing.requestStart)) timing.requestStart = timing.connectEnd + 1;
|
|
if (!Number.isFinite(timing.responseStart)) timing.responseStart = timing.requestStart + 4;
|
|
if (!Number.isFinite(timing.responseEnd)) timing.responseEnd = timing.responseStart + 4;
|
|
if (!Number.isFinite(timing.domLoading)) timing.domLoading = timing.responseEnd + 2;
|
|
if (!Number.isFinite(timing.domInteractive)) timing.domInteractive = timing.domLoading + 5;
|
|
if (!Number.isFinite(timing.domContentLoadedEventStart)) timing.domContentLoadedEventStart = timing.domInteractive + 1;
|
|
if (!Number.isFinite(timing.domContentLoadedEventEnd)) timing.domContentLoadedEventEnd = timing.domContentLoadedEventStart + 1;
|
|
if (!Number.isFinite(timing.domComplete)) timing.domComplete = timing.domContentLoadedEventEnd + 5;
|
|
if (!Number.isFinite(timing.loadEventStart)) timing.loadEventStart = timing.domComplete + 1;
|
|
if (!Number.isFinite(timing.loadEventEnd)) timing.loadEventEnd = timing.loadEventStart + 1;
|
|
|
|
return {
|
|
timeOrigin,
|
|
navigationStart,
|
|
nowOffset,
|
|
timing,
|
|
memory: cloneRecord(performanceSeed.memory),
|
|
};
|
|
}
|
|
|
|
function renderPerformancePatch(seed) {
|
|
const serializedSeed = JSON.stringify(seed, null, 2);
|
|
return `(
|
|
function installPerformanceModule(globalObject) {
|
|
const seed = ${serializedSeed};
|
|
const protector = globalObject.__envNativeProtector || null;
|
|
const baseEpoch = Date.now();
|
|
const monotonicStart = seed.nowOffset;
|
|
let tick = 0;
|
|
|
|
function nextNow() {
|
|
tick += 1;
|
|
return monotonicStart + (Date.now() - baseEpoch) + tick * 0.01;
|
|
}
|
|
|
|
function now() {
|
|
return Number(nextNow().toFixed(3));
|
|
}
|
|
|
|
function clearMarks() {
|
|
return undefined;
|
|
}
|
|
|
|
function clearMeasures() {
|
|
return undefined;
|
|
}
|
|
|
|
function getEntries() {
|
|
return [];
|
|
}
|
|
|
|
function getEntriesByType() {
|
|
return [];
|
|
}
|
|
|
|
function getEntriesByName() {
|
|
return [];
|
|
}
|
|
|
|
const performanceObject = {
|
|
now: now,
|
|
timeOrigin: seed.timeOrigin,
|
|
timing: seed.timing,
|
|
clearMarks: clearMarks,
|
|
clearMeasures: clearMeasures,
|
|
getEntries: getEntries,
|
|
getEntriesByType: getEntriesByType,
|
|
getEntriesByName: getEntriesByName,
|
|
mark: function mark() {},
|
|
measure: function measure() {}
|
|
};
|
|
|
|
if (seed.memory && Object.keys(seed.memory).length) {
|
|
performanceObject.memory = seed.memory;
|
|
}
|
|
|
|
if (protector && protector.repairFunctionMeta) {
|
|
protector.repairFunctionMeta(now, { name: "now", length: 0 });
|
|
protector.repairFunctionMeta(clearMarks, { name: "clearMarks", length: 0 });
|
|
protector.repairFunctionMeta(clearMeasures, { name: "clearMeasures", length: 0 });
|
|
protector.repairFunctionMeta(getEntries, { name: "getEntries", length: 0 });
|
|
protector.repairFunctionMeta(getEntriesByType, { name: "getEntriesByType", length: 1 });
|
|
protector.repairFunctionMeta(getEntriesByName, { name: "getEntriesByName", length: 1 });
|
|
}
|
|
if (protector && protector.protectFunction) {
|
|
protector.protectFunction(now, "now");
|
|
protector.protectFunction(clearMarks, "clearMarks");
|
|
protector.protectFunction(clearMeasures, "clearMeasures");
|
|
protector.protectFunction(getEntries, "getEntries");
|
|
protector.protectFunction(getEntriesByType, "getEntriesByType");
|
|
protector.protectFunction(getEntriesByName, "getEntriesByName");
|
|
}
|
|
|
|
Object.defineProperty(globalObject, "performance", {
|
|
configurable: true,
|
|
enumerable: false,
|
|
writable: true,
|
|
value: performanceObject
|
|
});
|
|
|
|
return performanceObject;
|
|
})(typeof globalThis !== "undefined" ? globalThis : window);
|
|
`;
|
|
}
|
|
|
|
function buildPerformanceModule(seed = {}) {
|
|
const module = scaffoldModule("performance-module");
|
|
const normalizedSeed = normalizePerformanceSeed(seed);
|
|
|
|
module.patchPlan = [
|
|
"构建 timeOrigin/navigationStart/timing 基础树。",
|
|
"补出单调递增的 performance.now()。",
|
|
"为 getEntries/getEntriesByType/getEntriesByName 等常见检测点提供稳定返回。",
|
|
];
|
|
module.patchCode = renderPerformancePatch(normalizedSeed);
|
|
module.runtimeState = {
|
|
performance: normalizedSeed,
|
|
};
|
|
module.validation = [
|
|
"连续两次调用 performance.now(),确认返回值递增。",
|
|
"检查 performance.timeOrigin 与 performance.timing.navigationStart 的相对关系。",
|
|
"检查 performance.getEntries()/getEntriesByType()/getEntriesByName() 返回数组。",
|
|
"检查 performance.now.toString() 以及 name/length 元信息。",
|
|
];
|
|
module.residualRisk = [
|
|
"未实现 PerformanceObserver、resource timing、navigation entries 等复杂链路。",
|
|
"如果目标脚本会校验 performance.memory 或多阶段时间戳分布,仍需采集真实浏览器种子。",
|
|
];
|
|
|
|
return module;
|
|
}
|
|
|
|
module.exports = {
|
|
buildPerformanceModule,
|
|
normalizePerformanceSeed,
|
|
};
|