Caliber.js Framework Library

一个旨在帮助开发者快速构建功能强大、可维护的现代油猴脚本的框架。它提供模块化架构、自动化的UI设置面板、响应式生命周期管理、高性能DOM调度器与网络请求拦截器等核心功能,让您专注于实现创意,而非繁琐的底层细节。

Tính đến 14-08-2025. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/545792/1641440/Caliberjs%20Framework%20Library.js

// ==UserScript==
// @name         Caliber.js Framework Library
// @namespace    You Boy
// @version      1.0.0
// @description  一个旨在帮助开发者快速构建功能强大、可维护的现代油猴脚本的框架。它提供模块化架构、自动化的UI设置面板、响应式生命周期管理、高性能DOM调度器与网络请求拦截器等核心功能,让您专注于实现创意,而非繁琐的底层细节。
// @author       You Boy
// @license      MIT
// @grant        none
// ==/UserScript==
((window) => {
  'use strict';

  if (window.Caliber) {
    console.warn('Caliber.js has already been loaded.');
    return;
  }

  const Caliber = (() => {
    'use strict';

    // #region --- 类型定义 (Type Definitions) ---
    /**
     * @typedef {object} DomBatchProcessorInstance 高性能DOM批量处理调度器实例
     * @property {(selector: string, callback: (node: HTMLElement) => void, options?: object) => Symbol} register - 注册一个DOM处理任务,返回一个唯一的任务ID。
     * @property {(taskId: Symbol) => void} unregister - 根据任务ID注销一个DOM处理任务。
     */

    /**
     * @typedef {object} ConfigManagerInstance 配置管理器实例
     * @property {() => object} getConfig - 获取当前已加载的配置对象。
     * @property {(path: string, value: any) => Promise<void>} updateAndSave - 按路径更新配置项并自动保存。
     * @property {() => Promise<void>} save - 手动将当前配置保存到存储中。
     */

    /**
     * @typedef {object} ModuleManagerInstance 模块管理器实例
     * @property {(ModuleClass: typeof Module) => void} register - 注册一个模块类。
     * @property {() => Module[]} getAllRegisteredModules - 获取所有已注册模块的元数据,主要用于UI生成。
     */

    /**
     * @typedef {object} LoggerInstance 日志记录器实例
     * @property {(message: any, ...args: any[]) => void} log - 输出一条标准日志。
     * @property {(message: any, ...args: any[]) => void} warn - 输出一条警告日志。
     * @property {(message: any, ...args: any[]) => void} error - 输出一条错误日志。
     * @property {(tag: string, styleOptions?: object) => LoggerInstance} createTaggedLogger - 创建一个带有自定义标签和样式的子日志记录器。
     */

    /**
     * @typedef {object} EventBusInstance 全局事件总线实例
     * @property {(eventName: string, callback: (data: any) => void) => void} on - 订阅一个事件。
     * @property {(eventName: string, callback: (data: any) => void) => void} off - 取消订阅一个事件。
     * @property {(eventName: string, data?: any) => void} emit - 发布一个事件。
     */

    /**
     * @typedef {object} StorageAdapter 存储服务适配器
     * @property {() => Promise<object>} get - 异步获取存储的配置对象。
     * @property {(value: object) => Promise<void>} set - 异步将配置对象写入存储。
     */

    /**
     * @typedef {object} StyleAdapter 样式服务适配器
     * @property {(cssString: string, id: string) => Promise<void>} add - 注入一段CSS样式,并关联一个唯一ID。
     * @property {(id: string) => void} remove - 根据ID移除之前注入的样式。
     */

    /**
     * @typedef {string} FetchInterceptorRequestString
     * 
     * 定义当一个网络请求被成功匹配时,在**发起实际请求之前**要执行的修改逻辑。
     * 这段代码在宿主页面的上下文中运行,拥有访问`window`对象的全部能力,但必须是一个无闭包依赖的纯字符串。
     * 
     *  示例:为一个API请求添加或修改一个查询参数
     * .onRequest(`
     *   [可用上下文]
     *   - url: {string} 原始的、完整的请求URL字符串。
     *   - config: {object} 原始的fetch配置对象 (如 method, headers, body 等)。
     *   - urlObject: {URL} 由框架预先创建的URL实例,强烈推荐使用它来操作URL。
     * 
     *   1. (推荐) 使用内置的 urlObject 来修改URL
     *   urlObject.searchParams.set('new_param', 'caliber_rocks');
     * 
     *   2. (可选) 修改 fetch 配置对象
     *   config.headers = { ...config.headers, 'X-Caliber-Injected': 'true' };
     *
     *   [返回契约]
     *   必须返回一个包含 'url' 和 'config' 键的对象。
     *   - url: {string} 最终将要被请求的URL字符串。
     *   - config: {object} 最终将要被使用的fetch配置对象。
     *   return { url: urlObject.toString(), config };
     * `)
     * 
     *  示例:添加或修改URL查询参数
     * > .onRequest(`
     *   [可用上下文]
     *   - url: {string} 原始的、完整的请求URL字符串。
     *   - config: {object} 原始的fetch配置对象 (如 method, headers, body 等)。
     *   - urlObject: {URL} 由框架预先创建的URL实例,强烈推荐使用它来操作URL。
     * 
     *   (推荐) 使用内置的 urlObject 来修改URL的查询参数
     *   > urlObject.searchParams.set('page', '1');
     *   > urlObject.searchParams.set('limit', '50');
     *
     *   必须返回一个包含 'url' 和 'config' 键的对象。
     *   > return { url: urlObject.toString(), config };
     * 
     * > `)
     * 
     */

    /**
     * @typedef {object} FetchInterceptorBuilder 网络请求拦截器构建器
     * @property {(handlerString: FetchInterceptorRequestString) => FetchInterceptorBuilder} onRequest - 定义请求被拦截时要执行的逻辑。
     * @property {(callback: (responseData: any) => void) => FetchInterceptorBuilder} onResponse - 定义在沙箱中处理响应数据的回调函数。
     * @property {(id: string|string[]) => void} register - 最终确定并注册这个拦截器。
     */

    /**
     * @typedef {object} PageScopeExecutorInstance 页面作用域代码执行器服务实例
     * @property {(codeString: string) => Promise<any>} execute - 在宿主页面环境中异步执行一段JS代码,并返回其可序列化的结果。
     */

    /**
     * @callback ResponseCallback
     * @param {any} responseData - 从注入脚本传回的、已解析的响应数据。
     */

    /**
     * @typedef {object} FetchInterceptorInstance 网络请求拦截器服务实例
     * @property {(urlOrOptions: string|{url: string, method?: string, match?: string|RegExp|Array<string|RegExp>}) => FetchInterceptorBuilder} target - [推荐] 启动一个链式调用来构建拦截器。
     * @property {(options: {targetUrl: string, method?: string, handler: string}) => string} createHook - (底层) 创建一个钩子函数字符串。
     * @property {(path: string[]|string, hookFunctionString: string, awaitsResponse?: boolean) => void} addHook - (底层) 添加一个仅修改请求的钩子。
     * @property {(path: string[]|string, hookFunctionString: string, responseCallback: ResponseCallback) => void} addHookWithResponse - (底层) 添加一个带响应回调的钩子。
     * @property {(path: string[]|string) => void} removeHook - (底层/通用) 按ID移除一个已注册的拦截器。
     */

    /**
     * @typedef {object} DOMSanitizerInstance DOM净化与安全注入服务实例
     * @property {(htmlString: string) => (TrustedHTML|string)} createTrustedHTML - 创建一个受信任的HTML对象(如果Trusted Types策略可用)。
     * @property {(element: Element, htmlString: string) => void} setInnerHTML - 安全地设置一个元素的innerHTML。
     * @property {(doc: Document, codeString: string) => void} injectScript - 安全地向文档注入一段JavaScript代码。
     * @property {(doc: Document, cssString: string, id: string) => (HTMLStyleElement|null)} injectStyle - 安全地向文档注入一段CSS样式。
     */

    /**
     * @typedef {object} FrameworkServices 框架核心服务集合
     * @property {DomBatchProcessorInstance} scheduler - 高性能DOM批量处理调度器。
     * @property {FetchInterceptorInstance} interceptor - 网络请求拦截器。
     * @property {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务。
     * @property {PageScopeExecutorInstance} executor - 页面作用域代码执行器。
     */

    /**
     * @typedef {object} CaliberServices 内核服务包
     * @property {LoggerInstance} logger - 日志服务实例。
     * @property {EventBusInstance} eventBus - 全局事件总线实例。
     * @property {Window} hostWindow - 宿主环境的 window 对象 (通常是 unsafeWindow)。
     * @property {Document} hostDocument - 宿主环境的 document 对象。
     * @property {StorageAdapter} storage - 存储服务适配器 (e.g., GM.getValue/setValue)。
     * @property {StyleAdapter} style - 样式服务适配器 (e.g., GM.addStyle)。
     * @property {string} APP_NAME - 当前应用的名称。
     * @property {ConfigManagerInstance} configManager - 配置管理器实例。
     * @property {ModuleManagerInstance} moduleManager - 模块管理器实例。
     * @property {FrameworkServices} framework - 框架核心服务集合。
     */

    /**
     * @typedef {object} ModuleInterface 模块类的公共API与内部属性接口
     * @property {string} id - 模块的唯一标识符,用于配置和管理。
     * @property {string} name - 模块的显示名称,用于UI。
     * @property {string} description - 模块的功能描述,用于UI。
     * @property {object} defaultConfig - 模块的默认配置。
     * @property {string|RegExp|Array<string|RegExp>|null} [match=null] - (可选) 限制模块仅在匹配该规则的页面上运行。
     * 
     * @property {CaliberServices} _services - (底层) 内核注入的完整核心服务集合。
     * @property {object} _config - 模块在总配置对象中的专属部分。
     * @property {LoggerInstance} _logger - (快捷方式) 日志记录器。
     * @property {EventBusInstance} _eventBus - (快捷方式) 事件总线。
     * @property {Window} _hostWindow - (快捷方式) 宿主 window 对象。
     * @property {Document} _hostDocument - (快捷方式) 宿主 document 对象。
     * @property {DomBatchProcessorInstance} _scheduler - (快捷方式) 高性能DOM批量处理调度器。
     * @property {FetchInterceptorInstance} _interceptor - (快捷方式) 网络请求拦截器。
     * @property {DOMSanitizerInstance} _sanitizer - (快捷方式) DOM净化与安全注入服务。
     * @property {PageScopeExecutorInstance} _executor - (快捷方式) 页面作用域代码执行器
     * 
     * @property {(context: {params: object, query: object}) => void} onEnable - 当模块被启用时调用。会收到包含解析后URL参数的上下文对象。
     * @property {() => void} onDisable - 当模块被禁用时调用。必须在此处清理所有副作用。
     * @property {(key: string, newValue: any, oldValue: any) => void} onConfigChange - 当模块的特定配置项发生变化时调用。
     */

    // #endregion

    /**
     * @class Module - 功能模块的标准化基类
     * 
     * 所有功能模块都应继承此类,以确保接口统一和生命周期管理。
     * @implements {ModuleInterface}
     */
    class Module {
      id = 'base-module';
      name = 'Base Module';
      description = '这是一个模块模板。';
      defaultConfig = {};
      match = null;

      _services;
      _config;
      _logger;
      _eventBus;
      _hostWindow;
      _hostDocument;
      _scheduler;
      _interceptor;
      _sanitizer;
      _executor;

      /**
       * @param {CaliberServices} services - 内核注入的核心服务。
       * @param {object} moduleConfig - 该模块在总配置中的专属配置部分。
       */
      constructor(services, moduleConfig) {
        if (!services) {
          return;
        }

        this._services = services;
        this._config = moduleConfig;
        this._logger = services.logger;
        this._eventBus = services.eventBus;
        this._hostWindow = services.hostWindow;
        this._hostDocument = services.hostDocument;

        // framework内部工具快捷方式
        this._scheduler = services.framework.scheduler;
        this._interceptor = services.framework.interceptor;
        this._sanitizer = services.framework.sanitizer;
        this._executor = services.framework.executor;
      }
      /**
       * 当模块被启用时调用。所有事件监听和DOM操作应在此处初始化。
       * @param {{params: object, query: object}} context - 包含从URL解析出的命名参数 (`params`) 和查询参数 (`query`) 的对象。
       */
      onEnable(context) {
        this._logger.warn(`Module '${this.id}' is missing the 'onEnable' implementation.`);
      }

      onDisable() { }

      /**
       * 当模块的特定配置项发生变化时调用。
       * @param {string} key - 发生变化的配置键。
       * @param {*} newValue - 新的配置值。
       * @param {*} oldValue - 旧的配置值。
       */
      onConfigChange(key, newValue, oldValue) { }
    }

    /**
     * @class AppKernel - 应用程序内核
     * 
     * 负责管理模块生命周期、配置、UI和所有核心服务。
     */
    class AppKernel {
      #services = {};
      #moduleManager;
      #configManager;
      #uiManager;
      #logger;
      #auditor = null;

      /**
       * @param {object} injectedServices - 由 `createApp` 组装好的所有核心服务和配置。
       * @param {Window} injectedServices.hostWindow - 宿主 window 对象。
       * @param {Document} injectedServices.hostDocument - 宿主 document 对象。
       * @param {EventBusInstance} injectedServices.eventBus - 全局事件总线。
       * @param {LoggerInstance} injectedServices.logger - 主日志记录器。
       * @param {StorageAdapter} injectedServices.storage - 存储服务适配器。
       * @param {StyleAdapter} injectedServices.style - 样式服务适配器。
       * @param {FrameworkServices} injectedServices.framework - 框架内部服务集合。
       * @param {string} injectedServices.APP_NAME - 应用名称。
       * @param {string} injectedServices.SAFE_APP_NAME - 安全的应用名称。
       * @param {boolean} injectedServices.IS_DEBUG - 是否为调试模式。
       * @param {object} injectedServices.initialConfig - 框架的初始配置。
       */
      constructor(injectedServices) {
        const {
          // --- 运行时服务 ---
          hostWindow,
          hostDocument,
          eventBus,
          logger,
          storage,
          style,
          framework,
          APP_NAME,
          SAFE_APP_NAME,
          // --- 元数据/配置 ---
          IS_DEBUG,
          initialConfig,
        } = injectedServices;

        this.#logger = logger;

        // 将“运行时服务”组装到内核的 #services 对象中
        this.#services = { hostWindow, hostDocument, eventBus, logger, storage, style, framework, APP_NAME };

        // 使用元数据进行初始化
        if (IS_DEBUG) {
          this.#auditor = new ModuleAuditor(this.#logger, hostWindow, SAFE_APP_NAME);
          this.#auditor?.patchScheduler(framework.scheduler);
        }

        // 创建并添加 Kernel 自身管理的核心服务实例
        this.#configManager = new ConfigManager(storage, logger, initialConfig);
        this.#moduleManager = new ModuleManager(this.#services, this.#auditor);
        this.#uiManager = new UIManager(this.#services);

        // 将新创建的管理器添加回 #services 包,以便所有模块都能访问它们
        this.#services.configManager = this.#configManager;
        this.#services.moduleManager = this.#moduleManager;

        this.#logger.log('Kernel constructed.');

        this.#services.eventBus.on('command:toggle-settings-panel', this.#handleToggleSettingsPanel);
        this.#services.eventBus.on('config-updated', this.#onConfigUpdated);
      }

      #handleToggleSettingsPanel = async () => {
        const currentConfig = this.#configManager.getConfig();
        if (!currentConfig) {
          this.#logger.error("Cannot toggle settings panel: config not loaded yet.");
          return;
        }
        const newState = !currentConfig.settingsPanel.enabled;
        await this.#configManager.updateAndSave('settingsPanel.enabled', newState);

        this.#services.eventBus.emit('config-updated', {
          path: 'settingsPanel.enabled',
          value: newState,
          newConfig: this.#configManager.getConfig()
        });
      }

      #onConfigUpdated = (detail) => {
        if (detail.path === 'settingsPanel.enabled') {
          this.#logger.log(`Settings Panel visibility changed to: ${detail.value}`);
          if (detail.value) {
            this.#uiManager.showPanelTrigger();
          } else {
            this.#uiManager.hidePanelTrigger();
          }
        }
      }

      /**
       * 启动应用。这是整个应用逻辑的入口点。
       */
      async run() {
        this.#logger.log('Kernel is running...');

        const moduleDefaultConfigs = this.#moduleManager.getAllDefaultConfigs();
        const finalConfig = await this.#configManager.loadAndGetConfig(moduleDefaultConfigs);
        this.#moduleManager.initializeActiveModules(finalConfig);
        this.#uiManager.init(finalConfig);

        this.#logger.log('Kernel run sequence complete.');
      }

      /**
       * 注册一个功能模块类。
       * @param {typeof Module} ModuleClass - 要注册的模块类 (注意是类本身,不是实例)。
       */
      registerModule(ModuleClass) {
        this.#moduleManager.register(ModuleClass);
      }
    }

    /**
     * @class ConfigManager - 配置管理器
     * 
     * 负责加载、合并、保存配置。
     */
    class ConfigManager {
      #storage;
      #logger;
      #config;
      #initialConfig;

      constructor(storage, logger, initialConfig) {
        this.#storage = storage;
        this.#logger = logger;
        this.#initialConfig = initialConfig;
      }

      /**
       * 加载、合并配置,并返回最终结果。
       * @param {object} moduleDefaultConfigs - 所有模块的默认配置集合。
       * @returns {Promise<object>} 最终的运行时配置。
       */
      async loadAndGetConfig(moduleDefaultConfigs) {
        const userConfig = await this.#storage.get();
        const baseConfig = { ...this.#initialConfig, ...moduleDefaultConfigs };
        let mergedConfig = this.#deepMerge(baseConfig, userConfig);
        this.#config = this.#prune(mergedConfig, baseConfig);

        this.#logger.log('Configuration loaded, merged, and pruned:', this.#config);
        return this.#config;
      }

      getConfig() {
        return this.#config;
      }

      /**
       * 通过路径更新配置树中的一个值,并触发保存。
       * @param {string} path - 要更新的配置路径,例如 'modules.themeSwitcher.theme'
       * @param {*} value - 新的配置值
       */
      async updateAndSave(path, value) {
        this.#set(this.#config, path, value);
        await this.save();
      }

      /**
       * 将当前配置保存到存储中。
       */
      async save() {
        await this.#storage.set(this.#config);
        this.#logger.log('Configuration saved.');
      }

      #deepMerge(target, source) {
        const output = { ...target };
        if (this.#isObject(target) && this.#isObject(source)) {
          for (const key in source) {
            const targetValue = target[key];
            const sourceValue = source[key];

            if (this.#isObject(targetValue) && this.#isObject(sourceValue)) {
              output[key] = this.#deepMerge(targetValue, sourceValue);
            }
            else if (this.#isObject(targetValue) && !this.#isObject(sourceValue)) {
              continue;
            }
            else {
              output[key] = sourceValue;
            }
          }
        }
        return output;
      }

      #isObject = (item) => (item && typeof item === 'object' && !Array.isArray(item));

      #set = (obj, path, value) => {
        const keys = path.split('.');
        const lastKey = keys.pop();
        const finalObj = keys.reduce((o, k) => o[k] = o[k] || {}, obj);
        finalObj[lastKey] = value;
      };

      /**
       * 以 template 为模板,递归地净化 source 对象。
       * 1. 移除所有不存在于 template 中的键。
       * 2. 检查值的类型,如果 source 中的值的类型与 template 不匹配,则强制回退到 template 的默认值。
       * 这是框架数据自愈能力的核心。
       * @param {object} source - 要被净化的对象 (例如,合并后的配置)。
       * @param {object} template - 权威的结构模板 (例如,默认基础配置)。
       * @returns {object} 净化后的新对象。
       * @private
       */
      #prune(source, template) {
        const prunedSource = {};

        for (const key in template) {
          if (source.hasOwnProperty(key)) {
            const sourceValue = source[key];
            const templateValue = template[key];

            const sourceType = typeof sourceValue;
            const templateType = typeof templateValue;

            // 核心净化逻辑:
            // 1. 如果类型匹配且都是对象,则递归净化。
            // 2. 如果类型匹配但不是对象,则接受用户的源值。
            // 3. 如果类型不匹配,则无条件地丢弃用户的源值,回退到模板的默认值。
            if (sourceType === templateType) {
              if (this.#isObject(sourceValue) && this.#isObject(templateValue)) {
                prunedSource[key] = this.#prune(sourceValue, templateValue);
              } else {
                prunedSource[key] = sourceValue; // 类型匹配,接受用户的值
              }
            } else {
              this.#logger.warn(`Configuration type mismatch for key '${key}'. User value (${sourceType}) discarded. Falling back to default (${templateType}).`);
              prunedSource[key] = templateValue; // 类型不匹配,强制回退
            }
          } else {
            // 如果用户的配置中缺少这个键,直接使用模板的默认值
            prunedSource[key] = template[key];
          }
        }
        // 遍历 source 中存在、但 template 中不存在的键,并将它们保留下来
        for (const key in source) {
          if (!template.hasOwnProperty(key)) {
            prunedSource[key] = source[key];
            if (this.#logger) this.#logger.log(`Dynamically added key '${key}' from user config has been preserved.`);
          }
        }
        return prunedSource;
      }
    }

    /**
     * @class ModuleManager - 模块管理器
     * 
     * 负责注册、实例化和管理所有模块的生命周期。
     */
    class ModuleManager {
      #services;
      #registeredModuleClasses = new Map();
      #activeModuleInstances = new Map();
      #auditor = null;

      constructor(services, auditor) {
        this.#services = services;
        this.#auditor = auditor;
        this.#services.eventBus.on('config-updated', this.#onConfigUpdated);
        this.#services.eventBus.on('navigate', this.#onNavigate);
      }

      /**
       * 注册一个模块类。
       * @param {typeof Module} ModuleClass
       */
      register(ModuleClass) {
        if (typeof ModuleClass !== 'function') {
          this.#services.logger.warn(`Attempted to register an invalid value. Expected a class.`, ModuleClass);
          return;
        }

        const tempInstance = new ModuleClass();

        // 校验模块实例是否符合最基本的规范 (必须有 id 和 name)
        const isValidId = tempInstance.id && typeof tempInstance.id === 'string' && tempInstance.id !== 'base-module';
        const hasValidName = tempInstance.name && typeof tempInstance.name === 'string';

        if (!isValidId || !hasValidName) {
          this.#services.logger.warn(`Attempted to register an invalid module. It must have a valid 'id' and 'name' property.`, tempInstance);
          return;
        }

        // 检查 ID 是否重复
        if (this.#registeredModuleClasses.has(tempInstance.id)) {
          this.#services.logger.warn(`Attempt to register module with duplicate ID: '${tempInstance.id}'. Skipping.`);
          return;
        }
        this.#registeredModuleClasses.set(tempInstance.id, ModuleClass);
        this.#services.logger.log(`Module class '${tempInstance.name} (${tempInstance.id})' registered.`);
      }

      /**
       * 根据最终配置,初始化所有应该被激活的模块。
       * @param {object} config - 最终的运行时配置。
       */
      initializeActiveModules(config) {
        this.#services.logger.log('Initializing active modules...');
        this.#onNavigate();
      }

      /**
       * 遍历所有已注册的模块类,收集它们的默认配置。
       * @returns {object} 一个包含所有模块默认配置的聚合对象。
       */
      getAllDefaultConfigs() {
        const modulesConfig = {};
        for (const ModuleClass of this.#registeredModuleClasses.values()) {
          const tempInstance = new ModuleClass();

          const processedDefaultConfig = {};
          for (const key in tempInstance.defaultConfig) {
            const item = tempInstance.defaultConfig[key];
            if (typeof item === 'object' && item !== null && 'value' in item) {
              processedDefaultConfig[key] = item.value;
            } else {
              processedDefaultConfig[key] = item;
            }
          }

          const initialEnabledState = processedDefaultConfig.enabled === true;

          delete processedDefaultConfig.enabled;

          modulesConfig[tempInstance.id] = {
            enabled: initialEnabledState,
            ...processedDefaultConfig,
          };

        }
        return { modules: modulesConfig };
      }

      /**
       * 返回所有已注册模块类的实例数组,用于UI生成。
       * @returns {Module[]}
       */
      getAllRegisteredModules() {
        const modules = [];
        for (const ModuleClass of this.#registeredModuleClasses.values()) {
          const moduleInstance = new ModuleClass();
          const clonedDefaultConfig = structuredClone(moduleInstance.defaultConfig);

          modules.push({
            id: moduleInstance.id,
            name: moduleInstance.name,
            description: moduleInstance.description,
            defaultConfig: clonedDefaultConfig,
          });
        }
        return modules;
      }

      /**
       * 当配置更新时被调用的核心响应函数
       * @param {object} detail - 包含 { path, value, newConfig } 的事件数据
       */
      #onConfigUpdated = (detail) => {
        const { path, value, newConfig } = detail;

        const enabledMatch = path.match(/^modules\.([^.]+)\.enabled$/);
        if (enabledMatch) {
          const moduleId = enabledMatch[1];
          this.#revalidateModuleState(moduleId, newConfig);
          return;
        }

        const optionMatch = path.match(/^modules\.([^.]+)\.(.+)$/);
        if (optionMatch) {
          const moduleId = optionMatch[1];
          const key = optionMatch[2];

          const moduleInstance = this.#activeModuleInstances.get(moduleId);
          if (moduleInstance) {
            const oldConfig = { ...moduleInstance._config };
            moduleInstance._config[key] = value;
            moduleInstance.onConfigChange(key, value, oldConfig[key]);
          }
        }
      }

      /**
       * 封装的启用模块的逻辑
       */
      #enableModule(id, ModuleClass, config, matchContext) {
        if (this.#activeModuleInstances.has(id)) return;

        try {
          // 创建模块专用的服务门面
          const moduleConfig = config.modules[id];
          const configManagerFacade = {
            // 只暴露与当前模块相关的配置API
            getConfig: () => moduleConfig,
            updateAndSave: async (key, value) => {
              // 自动为路径添加模块ID前缀,防止跨模块修改
              const path = `modules.${id}.${key}`;
              await this.#services.configManager.updateAndSave(path, value);

              // 触发全局事件,以保持UI同步等
              this.#services.eventBus.emit('config-updated', {
                path,
                value,
                newConfig: this.#services.configManager.getConfig()
              });
            }
          };

          const moduleServicesFacade = {
            ...this.#services, // 继承所有安全的服务
            configManager: configManagerFacade, // 覆盖为安全的门面
            moduleManager: null, // 彻底隐藏ModuleManager,模块不应该直接操作它
          };

          // 使用安全的门面对象来实例化模块
          const moduleInstance = new ModuleClass(moduleServicesFacade, moduleConfig);

          this.#auditor?.auditStart(id);
          moduleInstance.onEnable(matchContext);
          this.#auditor?.auditEnd();

          this.#activeModuleInstances.set(id, moduleInstance);
          this.#services.logger.log(`Module '${id}' dynamically ENABLED.`);
        } catch (e) {
          this.#services.logger.error(`Failed to dynamically enable module '${id}'.`, e);
        }
      }

      /**
       * 封装的禁用模块的逻辑
       */
      #disableModule(id) {
        const moduleInstance = this.#activeModuleInstances.get(id);

        if (!moduleInstance) return;

        try {
          // 植入审计钩子
          this.#auditor?.auditStart(id);
          moduleInstance.onDisable();
          this.#auditor?.auditEnd();

          // 从活动实例映射中移除,并允许垃圾回收
          this.#activeModuleInstances.delete(id);

          // 在模块禁用后,运行泄漏检查
          this.#auditor?.runChecks(id);

          this.#services.logger.log(`Module '${id}' dynamically DISABLED.`);
        } catch (e) {
          this.#services.logger.error(`Failed to dynamically disable module '${id}'.`, e);
        }
      }

      /**
       * 在SPA导航时触发,重新评估所有模块的match状态。
       * @private
       */
      #onNavigate = () => {
        this.#services.logger.log('Navigation detected, re-evaluating all module states...');
        const currentConfig = this.#services.configManager.getConfig();
        if (!currentConfig) return;

        for (const id of this.#registeredModuleClasses.keys()) {
          this.#revalidateModuleState(id, currentConfig);
        }
      }

      /**
       * 重新评估一个模块的最终状态(启用/禁用),并执行相应操作。
       * 这是模块生命周期管理的唯一决策点。
       * @param {string} id - 模块ID
       * @param {object} config - 当前的全局配置
       * @private
       */
      #revalidateModuleState(id, config) {
        const ModuleClass = this.#registeredModuleClasses.get(id);
        if (!ModuleClass) return;

        const isEnabledInConfig = config.modules[id]?.enabled; // 用户配置
        const tempInstance = new ModuleClass();
        const matchResult = _CaliberInternals._checkMatch(tempInstance.match, this.#services.hostWindow);

        const shouldBeActive = isEnabledInConfig && !!matchResult;
        const isActiveNow = this.#activeModuleInstances.has(id);

        if (shouldBeActive && !isActiveNow) {
          this.#enableModule(id, ModuleClass, config, matchResult);
        } else if (!shouldBeActive && isActiveNow) {
          this.#disableModule(id);
        }
      }
    }

    /**
     * @class UIManager - UI管理器
     * 
     * 负责创建、管理和销毁UI组件,如下方的设置面板。
     */
    class UIManager {
      #appName = 'CaliberApp';
      #services;
      #logger;
      #hostDocument;
      #hostWindow;
      #panelElement = null;
      #lastKnownConfig = null;
      #guardianIntervalId = null;
      #sanitizer;

      /**
       * @param {CaliberServices} services 
       */
      constructor(services) {
        this.#services = services;
        this.#logger = services.logger;
        this.#hostDocument = services.hostDocument;
        this.#hostWindow = services.hostWindow;
        this.#appName = services.APP_NAME || this.#appName;
        this.#sanitizer = services.framework.sanitizer;
        this.#defineSettingsPanelComponent(this.#sanitizer);
      }

      /**
       * 根据最终配置决定是否显示设置面板的触发按钮
       * @param {object} finalConfig - 从Kernel传入的最终运行时配置
       */
      init(finalConfig) {
        this.#lastKnownConfig = finalConfig;
        if (finalConfig.settingsPanel.enabled) {
          this.showPanelTrigger();
        }
      }

      /**
       * 创建并显示设置面板的触发按钮
       */
      showPanelTrigger() {
        if (this.#panelElement) return;

        this.#logger.log('Showing settings panel trigger.');
        this.#panelElement = this.#hostDocument.createElement('settings-panel');
        const modules = this.#services.moduleManager.getAllRegisteredModules();
        this.#panelElement.setData(
          modules,
          this.#lastKnownConfig,
          this.#services.configManager,
          this.#services.eventBus,
          this.#hostWindow,
        );

        // 首次挂载
        this.#hostDocument.documentElement.appendChild(this.#panelElement);
        // 启动守护者
        this.#startGuardian();
      }

      hidePanelTrigger() {
        // 隐藏时,必须停止守护者
        this.#stopGuardian();
        if (this.#panelElement) {
          this.#panelElement.remove();
          this.#panelElement = null;
        }
        this.#logger.log('Settings panel trigger hidden.');
      }

      /**
       * 启动一个定时器,在一段时间内持续检查面板是否存在,如果被移除则重新挂载。
       * @private
       */
      #startGuardian() {
        // 先确保之前的守护者已停止
        this.#stopGuardian();

        const checkInterval = 500; // 每500毫秒检查一次
        const duration = 10000; // 守护10秒,足以覆盖绝大多数SPA的加载时间
        let elapsedTime = 0;

        this.#guardianIntervalId = setInterval(() => {
          if (!this.#panelElement) {
            // 如果面板被hidePanelTrigger正常移除了,就停止守护
            this.#stopGuardian();
            return;
          }

          // 检查我们的组件是否还在 html 元素中
          if (!this.#hostDocument.documentElement.contains(this.#panelElement)) {
            this.#logger.warn('Settings panel was removed from DOM. Re-appending...');
            this.#hostDocument.documentElement.appendChild(this.#panelElement);
          }

          elapsedTime += checkInterval;
          if (elapsedTime >= duration) {
            this.#logger.log('Guardian period ended. Panel is stable.');
            this.#stopGuardian(); // 停止检查,节省资源
          }
        }, checkInterval);
      }

      /**
       * 停止守护定时器。
       * @private
       */
      #stopGuardian() {
        if (this.#guardianIntervalId) {
          clearInterval(this.#guardianIntervalId);
          this.#guardianIntervalId = null;
        }
      }

      /**
       * 定义 <settings-panel> Web Component
       */
      #defineSettingsPanelComponent(sanitizer) {
        if (customElements.get('settings-panel')) return;

        const APP_NAME = this.#appName;

        class SettingsPanel extends HTMLElement {
          constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            this._isOpen = false;
            this._modules = [];
            this._config = {};
            this._configManager = null;
            this._eventBus = null;
            this._boundOpenPanel = this.openPanel.bind(this);
            this._boundClosePanel = this.closePanel.bind(this);
            this._eventsBound = false;
          }

          // 外部数据注入接口
          setData(modules, config, configManager, eventBus, hostWindow) {
            this._modules = modules;
            this._config = config;
            this._configManager = configManager;
            this._eventBus = eventBus;
            this._hostWindow = hostWindow;
            this.render(); // 数据注入后重新渲染
          }

          connectedCallback() {
            if (!this.shadowRoot.firstChild) {
              this.render();
            }

            if (!this._eventsBound) {
              this.#addEventListeners();
              this._eventsBound = true;
            }

            this.#applyTheme();
          }

          /**
           * 当组件从DOM中移除时被调用,这是清理内存的关键。
           */
          disconnectedCallback() {
            this.#removeEventListeners();
            this._eventsBound = false;
          }

          /**
           * 集中添加所有事件监听器。
           * @private
           */
          #addEventListeners() {
            this.shadowRoot.querySelector('.trigger-btn').addEventListener('click', this._boundOpenPanel);
            this.shadowRoot.querySelector('.overlay').addEventListener('click', this._boundClosePanel);
            this.shadowRoot.querySelector('.drawer-content').addEventListener('change', this.#handleInputChange);
          }

          /**
           * 集中移除所有事件监听器,防止内存泄漏。
           * @private
           */
          #removeEventListeners() {
            const triggerBtn = this.shadowRoot.querySelector('.trigger-btn');
            if (triggerBtn) triggerBtn.removeEventListener('click', this._boundOpenPanel);

            const overlay = this.shadowRoot.querySelector('.overlay');
            if (overlay) overlay.removeEventListener('click', this._boundClosePanel);

            const content = this.shadowRoot.querySelector('.drawer-content');
            if (content) content.removeEventListener('change', this.#handleInputChange);
          }

          /**
           * input change 事件的统一处理函数。
           * @private
           */
          #handleInputChange = (e) => {
            const target = e.target;
            if (!target.dataset.configPath) return;

            const path = target.dataset.configPath;
            let value;
            switch (target.type) {
              case 'checkbox': value = target.checked; break;
              case 'number': value = Number(target.value); break;
              case 'text':
              case 'select-one':
              case 'color':
              default: value = target.value; break;
            }
            this.handleConfigChange(path, value);
          }

          openPanel = () => {
            if (this._isOpen) return;
            this.#applyTheme();
            this._isOpen = true;

            this.shadowRoot.querySelector('.drawer').classList.add('open');
            this.shadowRoot.querySelector('.overlay').classList.add('open');
            this.shadowRoot.querySelector('.trigger-btn').classList.add('hidden');
          }

          closePanel = () => {
            if (!this._isOpen) return;
            this._isOpen = false;

            this.shadowRoot.querySelector('.drawer').classList.remove('open');
            this.shadowRoot.querySelector('.overlay').classList.remove('open');
            this.shadowRoot.querySelector('.trigger-btn').classList.remove('hidden');
          }

          handleConfigChange = async (path, value) => {
            await this._configManager.updateAndSave(path, value);

            // 通知所有模块配置已更新
            this._eventBus.emit('config-updated', {
              path,
              value,
              newConfig: this._configManager.getConfig()
            });
          }

          /**
           * 检查系统颜色模式并为面板应用相应的主题。
           * @private
           */
          #applyTheme() {
            const drawer = this.shadowRoot.querySelector('.drawer');
            if (!drawer) return;

            const isSystemDark = this._hostWindow.matchMedia('(prefers-color-scheme: dark)').matches;

            if (isSystemDark) {
              drawer.dataset.theme = 'dark';
            } else {
              drawer.dataset.theme = 'light';
            }
          }

          render() {
            const styles = `
          :host {
              position: fixed;
              bottom: 25px;
              right: 25px;
              z-index: 9999;
              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
          }

          /* --- 1. 触发按钮 --- */
          .trigger-btn {
              width: 42px;
              height: 42px;
              border-radius: 12px 0 0 12px;
              border: none;
              display: flex;
              align-items: center;
              justify-content: center;
              cursor: pointer;
              transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), background-color 0.2s;
              opacity: 1;
              visibility: visible;
              background-color: rgba(255, 255, 255, 0.2);
              backdrop-filter: blur(6px);
              -webkit-backdrop-filter: blur(10px);
              box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
              position: fixed;
              right: 0;
              bottom: 50px;
          }

          .trigger-btn:hover {
              transform: scale(1.1);
              background-color: rgba(255, 255, 255, 0.3);
          }
          
          .trigger-btn .icon {
              font-size: 24px;
              color: #1d1d1f;
          }

          .trigger-btn.hidden {
              opacity: 0;
              visibility: hidden;
              transform: scale(0.8);
              pointer-events: none;
          }

          /* --- 2. 遮罩层 --- */
          .overlay {
              position: fixed;
              inset: 0;
              background-color: rgba(0, 0, 0, 0.45);
              opacity: 0;
              visibility: hidden;
              transition: opacity 0.3s ease, visibility 0.3s;
              cursor: pointer;
          }

          .overlay.open {
              opacity: 1;
              visibility: visible;
          }

          /* --- 3. 抽屉面板 --- */
          .drawer {
              position: fixed;
              top: 0;
              right: -100%;
              width: 360px;
              height: 100%;
              display: flex;
              flex-direction: column;
              transition: right 0.35s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s, box-shadow 0.3s;
              box-shadow: -4px 0 20px rgba(0,0,0,0.1);
              
              --bg-primary: #f3f4f5;
              --bg-secondary: #ffffff;
              --bg-tertiary: #f7f8f9;
              --bg-hover: #f7f8f9;
              --bg-input: #f7f8f9;
              --bg-input-focus: #ffffff;
              --bg-switch: #e5e7eb;
              --bg-switch-checked: #006ef4;
              
              --text-primary: #14191e;
              --text-secondary: #64696e;
              --text-tertiary: #8c9196;
              --text-placeholder: #b0b5b9;

              --border-primary: #e5e7eb;
              --border-secondary: #dadde0;
              --border-focus: #006ef4;
              --border-focus-shadow: rgba(0, 110, 244, 0.2);

              --shadow-primary: -4px 0 20px rgba(0,0,0,0.1);
              
              background-color: var(--bg-primary);

              --border-focus-shadow: rgba(0, 110, 244, 0.2);
              --select-arrow-svg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2364696e' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
          }

          .drawer[data-theme="dark"] {
              --bg-primary: #1c1c1e;
              --bg-secondary: #2c2c2e;
              --bg-tertiary: #3a3a3c;
              --bg-hover: #3a3a3c;
              --bg-input: #3a3a3c;
              --bg-input-focus: #1c1c1e;
              --bg-switch: #3a3a3c;
              --bg-switch-checked: #0a84ff;
              
              --text-primary: #f5f5f7;
              --text-secondary: #aeaeb2;
              --text-tertiary: #8e8e93;
              --text-placeholder: #636366;

              --border-primary: #3a3a3c;
              --border-secondary: #545458;
              --border-focus: #0a84ff;
              --border-focus-shadow: rgba(10, 132, 255, 0.2);
              
              --shadow-primary: -4px 0 20px rgba(0,0,0,0.3);
              
              --border-focus-shadow: rgba(10, 132, 255, 0.2);
              --select-arrow-svg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23aeaeb2' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
          }

          .drawer {
              box-shadow: var(--shadow-primary);
          }

          .drawer.open {
              right: 0;
          }
          
          .drawer-header {
              display: flex;
              justify-content: space-between;
              align-items: center;
              padding: 16px 20px;
              flex-shrink: 0;
              transition: background-color 0.3s, border-color 0.3s;
              background-color: var(--bg-secondary);
              border-bottom: 1px solid var(--border-secondary);
          }

          .drawer-header h2 {
              margin: 0;
              font-size: 16px;
              font-weight: 600;
              color: var(--text-primary);
              transition: color 0.3s;
          }

          .drawer-content {
              flex-grow: 1;
              overflow-y: auto;
              padding: 16px;
          }
          
          /* --- 4. 表单与布局 --- */
          .details-wrapper {
              border-radius: 8px;
              margin-bottom: 12px;
              overflow: hidden;
              transition: background-color 0.3s, border-color 0.3s;
              background-color: var(--bg-secondary);
              border: 1px solid var(--border-primary);
          }
          
          summary {
              list-style: none;
              display: block;
              cursor: pointer;
              padding: 16px 20px;
              transition: background-color 0.2s;
          }
          summary::-webkit-details-marker {
              display: none;
          }
          summary:hover {
              background-color: var(--bg-hover);
          }
          .details-wrapper[noconfig] > summary {
              cursor: default;
          }
          .details-wrapper[noconfig] > summary {
              cursor: default;
          }
          .details-wrapper[open][noconfig] > .sub-items-container,
          .sub-items-container:empty {
              display: none;
          }

          .details-wrapper[open]:not([noconfig]) > summary {
              border-bottom: 1px solid var(--border-primary);
          }
          
          .summary-content {
              display: flex;
              align-items: center;
              margin-bottom: 0;
          }
          
          .form-info {
              margin-left: 16px;
              padding-top: 0;
              flex: 1;
          }

          .form-info h4 {
              margin: 0 0 4px;
              font-size: 14px;
              font-weight: 500;
              display: flex;
              justify-content: space-between;
              align-items: center;
              transition: color 0.3s;
              color: var(--text-primary);
          }
          .form-info h4 span {
              font-size: 12px;
              margin-left: 8px;
              transition: color 0.3s;
              color: var(--text-tertiary);
          }

          .form-info p {
              margin: 0;
              font-size: 12px;
              transition: color 0.3s;
              color: var(--text-tertiary);
          }
          
          .sub-items-container {
              padding: 16px 20px;
          }
          
          .sub-item {
              display: flex;
              flex-direction: column;
              justify-content: space-between;
              margin-bottom: 16px;
          }

          .sub-item:last-child {
              margin-bottom: 0;
          }
          
          .sub-item-label {
              font-size: 14px;
              flex: 1;
              transition: color 0.3s;
              color: var(--text-secondary);
          }
          
          .sub-item-info {
              flex-grow: 1;
              display: flex;
              justify-content: space-between;
              align-items: center;
          }
          
          .sub-item-desc {
              font-size: 12px;
              margin-top: 6px;
              padding: 0;
              transition: color 0.3s;
              color: var(--text-tertiary);

              p {
                  margin: 0;
                  line-height: 1.2;
              }
          }

          /* --- 5. Switch 开关样式 --- */
          .switch {
              position: relative;
              display: inline-block;
              width: 40px;
              height: 22px;
              flex-shrink: 0;
          }
          .switch input { opacity: 0; width: 0; height: 0; }
          .slider {
              position: absolute;
              cursor: pointer;
              inset: 0;
              border-radius: 22px;
              transition: .3s;
              background-color: var(--bg-switch);
          }
          .slider:before {
              position: absolute;
              content: "";
              height: 18px;
              width: 18px;
              left: 2px;
              bottom: 2px;
              background-color: white;
              border-radius: 50%;
              box-shadow: 0 1px 2px rgba(0,0,0,0.1);
              transition: .3s;
          }
          input:checked + .slider {
              background-color: var(--bg-switch-checked);
          }
          input:checked + .slider:before {
              transform: translateX(18px);
          }

          /* --- 6. Text/Number 输入框样式 --- */
          .text-input {
              appearance: none;
              -webkit-appearance: none;
              min-height: 30px;
              width: 120px;
              padding: 6px 10px;
              border-radius: 6px;
              font-size: 14px;
              outline: none;
              text-align: right;
              box-sizing: border-box;
              transition: all 0.2s ease-in-out;
              border: 1px solid var(--border-secondary);
              background-color: var(--bg-input);
              color: var(--text-primary);
          }

          .text-input:hover {
              border-color: var(--border-primary);
              background-color: var(--bg-tertiary);
          }

          .text-input:focus {
              border-color: var(--border-focus);
              background-color: var(--bg-input-focus);
              box-shadow: 0 0 0 3px var(--border-focus-shadow);
          }

          select.text-input {
              text-align: left;
              text-align-last: left;
              padding-right: 30px; /* 为箭头留出空间 */
              background-image: var(--select-arrow-svg);
              background-repeat: no-repeat;
              background-position: right 0.7rem center;
              background-size: 0.9em 0.9em;
          }

          input[type="color"].text-input {
              width: 50px;
              min-height: 34px;
              padding: 4px;
              background-color: transparent;
          }
          
          input[type="color"].text-input::-webkit-color-swatch-wrapper {
              padding: 0;
          }

          input[type="color"].text-input::-webkit-color-swatch {
              border: none;
              border-radius: 4px;
          }

          input[type="color"].text-input::-moz-color-swatch {
              border: none;
              border-radius: 4px;
          }

          /* --- 7. 其他辅助样式 --- */
          .info-only { padding: 0 8px 8px; margin-bottom: 0; }
          .info-text { font-size: 12px; text-align: center; width: 100%; margin: 0; transition: color 0.3s; color: var(--text-tertiary); }
          .no-sub-config-text { font-size: 12px; text-align: center; padding: 8px 0; color: var(--text-placeholder); }
          .main-divider { display: none; }
      `;

            const triggerButtonHTML = `<button class="trigger-btn">⚙️</button>`;

            const drawerHTML = `
            <div class="overlay"></div>
            <div class="drawer">
                <div class="drawer-header">
                    <h2>${APP_NAME} 设置</h2>
                </div>
                <div class="drawer-content">
                    ${this.generateFormHTML()}
                </div>
            </div>
        `;

            const fullHTML = `
            <style>${styles}</style>
            ${triggerButtonHTML}
            ${drawerHTML}
          `;
            sanitizer.setInnerHTML(this.shadowRoot, fullHTML);

            this.#syncUIWithConfig();
          }

          generateFormHTML() {
            let html = '';

            html += `<div class="form-group info-only"><p class="info-text">所有设置修改后将自动保存,部分设置需刷新页面生效。</p></div>`;

            for (const module of this._modules) {
              const moduleConfig = this._config.modules[module.id];
              if (!moduleConfig) continue;

              const configKeys = Object.keys(module.defaultConfig);
              const nonEnabledConfigKeys = configKeys.filter(key => key !== 'enabled');
              const optionCount = nonEnabledConfigKeys.length;

              html += `<details class="details-wrapper" ${optionCount === 0 ? 'noconfig' : ''}>
                     <summary>`;
              html += `<div class="form-group summary-content">
                     <label class="switch">
                       <input type="checkbox" data-config-path="modules.${module.id}.enabled" data-needs-reload="true">
                       <span class="slider"></span>
                     </label>
                     <div class="form-info">
                       <h4>${module.name}<span>${optionCount === 0 ? "" : " ⚙️"}</span></h4>
                       <p>${module.description}</p>
                     </div>
                   </div></summary>`;

              html += `<div class="sub-items-container">`;

              if (nonEnabledConfigKeys.length === 0) {
                html += ``;
              } else {
                for (const key of nonEnabledConfigKeys) {
                  const configItem = module.defaultConfig[key];
                  const value = moduleConfig[key];

                  const path = `modules.${module.id}.${key}`;
                  let inputHTML = '';
                  let labelText, controlType, itemDescription;

                  if (typeof configItem === 'object' && configItem !== null && 'value' in configItem) {
                    labelText = configItem.label || key;
                    controlType = configItem.type || 'string';
                    itemDescription = configItem.description || '';
                  } else {
                    labelText = key;
                    controlType = typeof configItem;
                    itemDescription = '';
                  }

                  switch (controlType) {
                    case 'boolean':
                      inputHTML = `<label class="switch"><input type="checkbox" data-config-path="${path}" ${value ? 'checked' : ''}><span class="slider"></span></label>`;
                      break;
                    case 'number':
                      inputHTML = `<input type="number" class="text-input" data-config-path="${path}" value="${value || 0}">`; // value: 如果不存在,默认为0
                      break;
                    case 'string':
                      inputHTML = `<input type="text" class="text-input" data-config-path="${path}" value="${value || ''}">`; // value: 如果不存在,默认为空字符串
                      break;
                    case 'select':
                      inputHTML = `<select class="text-input" data-config-path="${path}">`;
                      if (configItem.options && Array.isArray(configItem.options)) {
                        configItem.options.forEach(option => {
                          const selected = (option.value === value) ? 'selected' : '';
                          inputHTML += `<option value="${option.value}" ${selected}>${option.label || option.value}</option>`;
                        });
                      }
                      inputHTML += `</select>`;
                      break;
                    case 'color':
                      inputHTML = `<input type="color" class="text-input color-input" data-config-path="${path}" value="${value}">`;
                      break;
                    default:
                      html += `<div class="form-group sub-item"><span class="sub-item-label">${labelText}</span><p style="color:red;">(不支持的配置类型: ${controlType})</p></div>`;
                      continue; // 跳过不支持的类型
                  }

                  html += `
                <div class="form-group sub-item">
                  <div class="sub-item-info">
                    <label class="sub-item-label">${labelText}</label>
                    ${inputHTML}
                  </div>
                  ${itemDescription ? `<div class="sub-item-desc"><p>${itemDescription}</p></div>` : ''}
                </div>`;
                }
              }
              html += `</div></details><hr class="main-divider"/>`;
            }

            if (html.endsWith('<hr class="module-divider"/>')) {
              html = html.slice(0, -28);
            }
            return html;
          }


          /**
           * 遍历所有带 data-config-path 的输入框,并根据 this._config 设置其状态。
           * @private
           */
          #syncUIWithConfig() {
            this.shadowRoot.querySelectorAll('[data-config-path]').forEach(input => {
              const path = input.dataset.configPath;
              const keys = path.split('.');

              let value = this._config;
              for (const key of keys) {
                if (value === undefined || value === null) break;
                value = value[key];
              }

              if (value === undefined || value === null) return;

              switch (input.type) {
                case 'checkbox':
                  input.checked = Boolean(value);
                  break;
                case 'number':
                case 'text':
                case 'color':
                case 'select-one':
                  input.value = value;
                  break;
              }
            });
          }
        }

        customElements.define('settings-panel', SettingsPanel);
      }
    }

    /**
     * @class ModuleAuditor - (仅在Debug模式下激活) 模块审计员
     * 
     * 负责监控模块的副作用(事件监听、定时器),并在模块禁用后报告任何未被清理的资源泄漏。
     */
    class ModuleAuditor {
      #logger;
      #hostWindow;
      #originalAddEventListener;
      #originalRemoveEventListener;
      #originalSetInterval;
      #originalClearInterval;
      #originalSchedulerRegister;
      #originalSchedulerUnregister;
      #activeModuleId = null;
      #trackedResources = new Map();

      constructor(logger, hostWindow, SAFE_APP_NAME) {
        this.#logger = logger.createTaggedLogger('Auditor', { backgroundColor: '#9C27B0' });
        this.#hostWindow = hostWindow;
        this.#patchGlobalApis(SAFE_APP_NAME);
        this.#logger.warn('Module Auditor is active. Resource leakage will be reported.');
      }

      /**
       * 在 scheduler 实例创建后,由 Kernel 调用,用于代理其方法。
       * @param {DomBatchProcessorInstance} schedulerInstance 
       */
      patchScheduler(schedulerInstance) {
        if (!schedulerInstance) return;

        this.#originalSchedulerRegister = schedulerInstance.register;
        this.#originalSchedulerUnregister = schedulerInstance.unregister;

        const self = this;

        schedulerInstance.register = function (selector, callback, options) {
          const taskId = self.#originalSchedulerRegister.call(this, selector, callback, options);
          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            resources.schedulers.add(taskId);
          }
          return taskId;
        };

        schedulerInstance.unregister = function (taskId) {
          // 全局查找并删除,因为 unregister 可能在模块上下文之外被调用
          for (const resources of self.#trackedResources.values()) {
            if (resources.schedulers.has(taskId)) {
              resources.schedulers.delete(taskId);
              break;
            }
          }
          return self.#originalSchedulerUnregister.call(this, taskId);
        };

        this.#logger.log('Scheduler has been patched for auditing.');
      }

      /**
       * 在调用模块的 onEnable/onDisable 之前调用,设置审计上下文。
       * @param {string} moduleId - 正在被审计的模块ID。
       */
      auditStart(moduleId) {
        this.#activeModuleId = moduleId;
      }

      /**
       * 在调用模块的 onEnable/onDisable 之后调用,清除审计上下文。
       */
      auditEnd() {
        this.#activeModuleId = null;
      }

      /**
       * 在模块被禁用后,运行泄漏检查。
       * @param {string} moduleId - 已被禁用的模块ID。
       */
      runChecks(moduleId) {
        const resources = this.#trackedResources.get(moduleId);
        if (!resources) return;

        let leaksFound = 0;

        // 修正: 检查事件监听器泄漏
        if (resources.events.size > 0) {
          leaksFound += resources.events.size;
          resources.events.forEach((types, target) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': Event listener(s) for type(s) [${[...types].join(', ')}] were NOT removed from element:`, target);
          });
        }

        // 检查定时器泄漏
        if (resources.intervals.size > 0) {
          leaksFound += resources.intervals.size;
          resources.intervals.forEach((id) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': A setInterval (ID: ${id}) was NOT cleared.`);
          });
        }

        // 检查调度器任务泄漏
        if (resources.schedulers.size > 0) {
          leaksFound += resources.schedulers.size;
          resources.schedulers.forEach((id) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': A scheduler task (ID: ${id.toString()}) was NOT unregistered.`);
          });
        }

        if (leaksFound === 0) {
          this.#logger.log(`Module '${moduleId}' passed audit. All tracked resources were cleaned up.`);
        }

        this.#trackedResources.delete(moduleId);
      }

      #initializeTracking(moduleId) {
        if (!this.#trackedResources.has(moduleId)) {
          this.#trackedResources.set(moduleId, {
            events: new Map(),
            intervals: new Set(),
            schedulers: new Set()
          });
        }
        return this.#trackedResources.get(moduleId);
      }

      #patchGlobalApis(SAFE_APP_NAME) {
        this.#originalAddEventListener = EventTarget.prototype.addEventListener;
        this.#originalRemoveEventListener = EventTarget.prototype.removeEventListener;
        this.#originalSetInterval = this.#hostWindow.setInterval;
        this.#originalClearInterval = this.#hostWindow.clearInterval;

        const self = this;
        const namespace = `__CALIBER_${SAFE_APP_NAME}`;
        const responseEventName = `${namespace}_RESPONSE`;

        // --- Patch addEventListener ---
        EventTarget.prototype.addEventListener = function (type, listener, options) {
          if (this === self.#hostWindow.document && type === responseEventName) {
            // 直接调用原始方法,不进行任何追踪
            return self.#originalAddEventListener.call(this, type, listener, options);
          }

          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            if (!resources.events.has(this)) {
              resources.events.set(this, new Set());
            }
            resources.events.get(this).add(type);
          }
          return self.#originalAddEventListener.call(this, type, listener, options);
        };

        // --- Patch removeEventListener ---
        EventTarget.prototype.removeEventListener = function (type, listener, options) {
          for (const resources of self.#trackedResources.values()) {
            if (resources.events.has(this)) {
              const types = resources.events.get(this);
              types.delete(type);
              if (types.size === 0) {
                resources.events.delete(this);
              }
              break;
            }
          }
          return self.#originalRemoveEventListener.call(this, type, listener, options);
        };

        // --- Patch setInterval ---
        this.#hostWindow.setInterval = function (handler, timeout) {
          const intervalId = self.#originalSetInterval.call(this, handler, timeout);
          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            resources.intervals.add(intervalId);
          }
          return intervalId;
        };

        // --- Patch clearInterval ---
        this.#hostWindow.clearInterval = function (id) {
          // 全局查找并删除,因为 clearInterval 可能在模块上下文之外被调用
          for (const resources of self.#trackedResources.values()) {
            if (resources.intervals.has(id)) {
              resources.intervals.delete(id);
              break;
            }
          }
          return self.#originalClearInterval.call(this, id);
        };
      }
    }

    /**
     * @class DomBatchProcessor - 高性能DOM批量处理调度器
     */
    class DomBatchProcessor {
      #observer = null;
      #taskQueue = [];
      #registeredTasks = new Map();
      #isLoopRunning = false;
      #isObserving = false;
      #batchSize;
      #logger;
      #hostDocument;

      /**
       * @param {number} batchSize - 在每个渲染帧中处理的最大任务数。
       * @param {LoggerInstance} logger - 用于日志输出的 logger 对象。
       * @param {Document} hostDocument - 宿主 document 对象。
       */
      constructor(batchSize, logger, hostDocument) {
        this.#batchSize = batchSize || 20;
        this.#logger = logger;
        this.#hostDocument = hostDocument;
        // 只创建 MutationObserver 实例,不立即启动监听(懒启动)
        this.#observer = new MutationObserver(this.#handleMutations);
      }

      /**
       * 注册一个DOM处理任务。
       * @param {string} selector - 用于匹配节点的CSS选择器。
       * @param {(node: HTMLElement) => void} callback - 匹配到节点时要执行的回调函数。
       * @param {object} [options] - (可选) 监听选项。
       * @param {boolean} [options.add=true] - 是否监听节点的添加。
       * @param {boolean} [options.attributes=false] - 是否监听节点属性的变化。
       * @param {string[]} [options.attributeFilter] - (可选) 只监听特定属性的变化。
       * @param {HTMLElement | string} [options.root] - (可选) 任务的根节点或根选择器。只有当DOM变动发生在此根节点内部时,任务才会被匹配。
       * @param {boolean} [options.processExisting=false] - (可选) 是否在注册时立即处理DOM中已存在的匹配节点。
       * @returns {Symbol} 一个唯一的任务ID,用于后续注销。
       */
      register(selector, callback, options = {}) {
        const taskId = Symbol(selector);
        this.#registeredTasks.set(taskId, {
          selector,
          callback,
          options: { add: true, ...options }
        });
        this.#logger.log(`Task registered for selector "${selector}"`, options.root ? `within root "${options.root}"` : '');

        if (options.processExisting) {
          const rootNode = (typeof options.root === 'string'
            ? this.#hostDocument.querySelector(options.root)
            : options.root) || this.#hostDocument;

          const existingNodes = rootNode.querySelectorAll(selector);

          if (existingNodes.length > 0) {
            this.#logger.log(`Explicitly processing ${existingNodes.length} existing node(s) for selector "${selector}".`);
            existingNodes.forEach(node => {
              // 直接推入队列,由 rAF 循环处理
              this.#taskQueue.push({ node, callback: callback });
            });
            // 确保处理循环启动
            this.#startLoop();
          }
        }

        this.#updateObserver();

        return taskId;
      }

      /**
       * 注销一个DOM处理任务。
       * @param {Symbol} taskId - 注册时返回的任务ID。
       */
      unregister(taskId) {
        if (this.#registeredTasks.has(taskId)) {
          const selector = this.#registeredTasks.get(taskId).selector;
          this.#registeredTasks.delete(taskId);
          this.#logger.log(`Task for selector "${selector}" unregistered.`);

          this.#updateObserver();
        }
      }

      /**
       * 根据当前所有注册任务的需求,动态计算并应用最优的 MutationObserver 配置。
       * 这是实现懒启动、自动休眠和动态功能切换的核心。
       * @private
       */
      #updateObserver() {
        if (this.#isObserving) {
          this.#observer.disconnect();
          this.#isObserving = false;
        }

        // 如果没有任何任务,则保持休眠状态
        if (this.#registeredTasks.size === 0) {
          this.#logger.log('Observer stopped as no tasks are registered.');
          return;
        }

        let needsChildList = false;
        let needsAttributes = false;
        const attributeFilterSet = new Set();
        let useAttributeFilter = true;

        // 遍历所有任务,聚合出最小的配置需求
        for (const task of this.#registeredTasks.values()) {
          if (task.options.add) needsChildList = true;
          if (task.options.attributes) {
            needsAttributes = true;
            if (task.options.attributeFilter && Array.isArray(task.options.attributeFilter)) {
              task.options.attributeFilter.forEach(attr => attributeFilterSet.add(attr));
            } else {
              // 只要有一个任务需要监听属性但未提供 filter,就不能启用 filter 优化
              useAttributeFilter = false;
            }
          }
        }

        const observerConfig = { subtree: true };
        if (needsChildList) observerConfig.childList = true;
        if (needsAttributes) {
          observerConfig.attributes = true;
          // 仅当所有需要监听属性的任务都指定了 filter 时,才应用 filter
          if (useAttributeFilter && attributeFilterSet.size > 0) {
            observerConfig.attributeFilter = Array.from(attributeFilterSet);
          }
        }

        // 只有在需要监听任何东西的情况下才启动观察
        if (observerConfig.childList || observerConfig.attributes) {
          this.#observer.observe(this.#hostDocument.documentElement, observerConfig);
          this.#isObserving = true;
          this.#logger.log('Observer (re)started with new config:', observerConfig);
        }
      }

      /**
       * MutationObserver的回调,负责过滤和排队任务。
       * @private
       */
      #handleMutations = (mutations) => {
        for (const mutation of mutations) {
          if (mutation.type === 'childList') {
            for (const addedNode of mutation.addedNodes) {
              // 只处理元素节点,忽略文本节点等
              if (addedNode.nodeType === Node.ELEMENT_NODE) {
                this.#queueMatchingTasks(addedNode, 'add');

                // 同时检查新增节点下的所有子元素是否也匹配
                const descendants = addedNode.querySelectorAll('*');
                for (const descendant of descendants) {
                  this.#queueMatchingTasks(descendant, 'add');
                }
              }
            }
          } else if (mutation.type === 'attributes') {
            if (mutation.target.nodeType === Node.ELEMENT_NODE) {
              this.#queueMatchingTasks(mutation.target, 'attributes', mutation.attributeName);
            }
          }
        }
        // 只要队列中有任务,就确保 rAF 循环在运行
        if (this.#taskQueue.length > 0) {
          this.#startLoop();
        }
      }

      /**
       * 辅助函数:检查一个节点是否匹配任何已注册的任务,并将其排队。
       * @private
       */
      #queueMatchingTasks(node, mutationType, attributeName = null) {
        for (const task of this.#registeredTasks.values()) {
          // --- 根节点靶向检查 ---
          if (task.options.root) {
            const rootNode = typeof task.options.root === 'string'
              ? this.#hostDocument.querySelector(task.options.root)
              : task.options.root;

            // 如果根节点不存在,或者变化节点不在根节点内部,则直接跳过此任务
            if (!rootNode || !rootNode.contains(node)) {
              continue;
            }
          }

          // 检查任务是否订阅了此类变更,并且节点是否匹配选择器
          if (task.options[mutationType] && node.matches(task.selector)) {
            // 对于属性变更,额外检查 attributeFilter
            if (mutationType === 'attributes' && task.options.attributeFilter) {
              if (!task.options.attributeFilter.includes(attributeName)) {
                continue; // 属性不匹配,跳过当前任务
              }
            }
            this.#taskQueue.push({ node, callback: task.callback });
          }
        }
      }

      /**
       * 启动 rAF 循环。
       * @private
       */
      #startLoop() {
        if (this.#isLoopRunning) return;
        this.#isLoopRunning = true;
        requestAnimationFrame(this.#processQueue);
      }

      /**
       * rAF 循环的核心,负责分批处理任务。
       * @private
       */
      #processQueue = () => {
        const batch = this.#taskQueue.splice(0, this.#batchSize);
        for (const task of batch) {
          try {
            // 在执行回调前,最好检查一下节点是否还在DOM中
            if (this.#hostDocument.documentElement.contains(task.node)) {
              task.callback(task.node);
            }
          } catch (e) {
            this.#logger.error('Error in DomBatchProcessor task callback:', e);
          }
        }

        if (this.#taskQueue.length > 0) {
          requestAnimationFrame(this.#processQueue);
        } else {
          this.#isLoopRunning = false;
        }
      }
    }

    /**
     * @class LoggerService - 专用的、可派生的日志服务
     */
    class LoggerService {
      #isDebug;
      #baseTagStyle = `color: white; padding: 2px 6px; border-radius: 4px; font-weight: bold;`;

      constructor(isDebug) {
        this.#isDebug = Boolean(isDebug);
      }

      /**
       * (私有辅助函数) 创建一组核心的日志方法。
       * @private
       */
      #createLogMethodsFor(tag, styles) {
        return {
          log: (message, ...args) => this.#isDebug && console.log(`%c${tag}`, styles.log, message, ...args),
          warn: (message, ...args) => this.#isDebug && console.warn(`%c${tag}`, styles.warn, message, ...args),
          error: (message, ...args) => console.error(`%c${tag}`, styles.error, message, ...args),
        };
      }

      /**
       * 创建一个主 logger 实例。
       */
      createMainLogger(appName) {
        const styles = {
          log: `background-color: #0057b8; ${this.#baseTagStyle}`,
          warn: `background-color: #ff9800; color: black; ${this.#baseTagStyle}`,
          error: `background-color: #f44336; ${this.#baseTagStyle}`,
        };
        const mainLogger = this.#createLogMethodsFor(appName, styles);

        mainLogger.createTaggedLogger = (tag, styleOptions = {}) => {
          const taggedStyles = {
            log: `background-color: ${styleOptions.backgroundColor || '#757575'}; color: ${styleOptions.color || 'white'}; ${this.#baseTagStyle}`,
            warn: `background-color: ${styleOptions.backgroundColor || '#757575'}; color: ${styleOptions.color || 'white'}; ${this.#baseTagStyle}`,
            error: `background-color: #f44336; color: white; ${this.#baseTagStyle}`, // 错误总是红色
          };
          return this.#createLogMethodsFor(tag, taggedStyles);
        };

        return mainLogger;
      }
    }

    /**
     * 框架内部工具
     */
    const _CaliberInternals = {
      /**
       * @private
       * [上下文创建器] 从应用名称派生出所有需要的上下文状态。
       * @param {string} appName - 原始的应用名称。
       * @returns {{safeAppName: string, instanceKey: string}} 包含派生状态的上下文对象。
       */
      _createAppContext: (appName) => {
        const utf8Bytes = new TextEncoder().encode(appName);
        let binaryString = '';
        utf8Bytes.forEach(byte => {
          binaryString += String.fromCharCode(byte);
        });
        let safeAppName = btoa(binaryString);
        safeAppName = safeAppName.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
        const instanceKey = `CALIBER_INSTANCE_${safeAppName}`;
        return { safeAppName, instanceKey };
      },

      /**
       * @private
       * [预检器] 对应用配置和运行环境执行所有预检。
       * @param {object} options - createApp 接收的原始选项。
       * @param {string} instanceKey - 由 _createAppContext 生成的实例键。
       * @param {object} logger - 用于报告错误的主 logger。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @returns {boolean} - 所有检查通过返回 true,否则返回 false。
       */
      runPreflightChecks: (options, instanceKey, logger, hostWindow) => {
        const { appName, modules, services } = options || {};

        if (!options || typeof options !== 'object' || !appName || typeof appName !== 'string' || !Array.isArray(modules) || !services || !services.storage || !services.command) {
          logger.error('Preflight check failed: Invalid configuration object.');
          return false;
        }

        if (modules.length === 0) {
          if (options.isDebug) logger.log(`Preflight check skipped: No modules provided.`);
          return false;
        }

        if (hostWindow[instanceKey]) {
          logger.warn('Preflight check failed: Script instance already running.');
          return false;
        }

        return true;
      },

      /**
       * 初始化所有框架核心服务。
       * @param {object} options - createApp 接收的原始选项。
       * @param {{safeAppName: string}} context - 部分上下文,包含 safeAppName。
       * @param {LoggerInstance} logger - 主 logger。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @param {Document} hostDocument - 宿主页面的 document 对象。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化服务实例。
       * @returns {{eventBus: EventBusInstance, framework: FrameworkServices}} - 包含事件总线和框架服务集合的对象。
       */
      initializeCoreServices: (options, context, logger, hostWindow, hostDocument, sanitizer) => {
        const eventBus = _CaliberInternals.createEventBus(logger);
        const schedulerLogger = logger.createTaggedLogger('Scheduler', { backgroundColor: '#4CAF50' });

        const frameworkServices = {
          scheduler: new DomBatchProcessor(options.framework?.domProcessorBatchSize, schedulerLogger, hostDocument),
          interceptor: _CaliberInternals.createFetchInterceptor(logger, hostWindow, hostDocument, context.safeAppName, options.isDebug, sanitizer),
          sanitizer: sanitizer,
          executor: _CaliberInternals.createPageScopeExecutor(logger, context.safeAppName, sanitizer, hostDocument),
        };

        _CaliberInternals.patchHistoryForNavigation(eventBus, hostWindow);
        return { eventBus, framework: frameworkServices };
      },

      /**
       * @private
       * 核心匹配引擎 - 检查给定的匹配规则是否与当前页面URL匹配。
       * @param {string|RegExp|Array<string|RegExp>|null|undefined} matchRule - 匹配规则。
       * @param {Window} hostWindow - 宿主 window 对象。
       * @returns {object|false} - 不匹配返回false,匹配返回 { params, query }。
       */
      _checkMatch: (matchRule, hostWindow) => {
        const currentUrl = new URL(hostWindow.location.href);

        // 处理查询参数
        const query = {};
        const searchParams = currentUrl.searchParams;
        for (const [key, value] of searchParams.entries()) {
          const existing = query[key];
          if (existing !== undefined) {
            query[key] = Array.isArray(existing)
              ? [...existing, value]
              : [existing, value];
          } else {
            query[key] = value;
          }
        }

        // 路径规范化
        const rawPathname = currentUrl.pathname;
        const pathname = rawPathname.endsWith('/') && rawPathname.length > 1
          ? rawPathname.slice(0, -1)
          : rawPathname;

        const href = currentUrl.href;

        // 空规则快速返回
        if (matchRule == null) {
          return { params: {}, query };
        }

        // 规则数组处理
        const rules = Array.isArray(matchRule) ? matchRule : [matchRule];

        // 核心匹配逻辑
        for (const rule of rules) {
          const result = checkRule(rule);
          if (result) return result;
        }

        return false;

        // 辅助函数保持内部作用域
        function checkRule(rule) {
          if (rule == null) return { params: {}, query };
          if (!rule) return false;

          // 正则表达式规则
          if (rule instanceof RegExp) {
            const match = rule.exec(pathname) || rule.exec(href);
            return match
              ? { params: match.groups || {}, query }
              : false;
          }

          // 字符串规则
          if (typeof rule === 'string') {
            let isAbsolute = false;
            let rulePath = rule;
            let ruleProtocol = '';
            let ruleHost = '';

            try {
              const urlObj = new URL(rule);
              isAbsolute = true;
              ruleProtocol = urlObj.protocol;
              ruleHost = urlObj.host;
              rulePath = urlObj.pathname;
            } catch { }

            if (isAbsolute) {
              if (ruleProtocol !== currentUrl.protocol || ruleHost !== currentUrl.host) {
                return false;
              }
            }

            // 路径规范化
            const normalizedRule = rulePath.endsWith('/') && rulePath.length > 1
              ? rulePath.slice(0, -1)
              : rulePath;

            // 快速前缀匹配(无参数路径)
            if (pathname === normalizedRule || pathname.startsWith(normalizedRule + '/')) {
              return { params: {}, query };
            }

            // 参数化路径匹配
            return matchParamPath(normalizedRule);
          }

          return false;
        }

        // 参数化路径匹配
        function matchParamPath(pattern) {
          // 检查是否需要参数匹配
          const hasParams = pattern.includes(':') || pattern.includes('*');
          if (!hasParams) return false;

          // 构建正则表达式
          const parts = pattern.split('/').slice(1);
          let regexStr = '^';
          const paramNames = [];
          let hasWildcard = false;

          for (const part of parts) {
            if (hasWildcard) return false; // 通配符后不能有其他部分

            if (part.startsWith(':')) {
              const isOptional = part.endsWith('?');
              const name = isOptional ? part.slice(1, -1) : part.slice(1);
              paramNames.push(name);
              regexStr += isOptional ? '(?:/([^/]+))?' : '/([^/]+)';
            }
            else if (part === '*') {
              paramNames.push('_');
              regexStr += '(?:/(.*))?';
              hasWildcard = true;
            }
            else {
              regexStr += '/' + part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            }
          }
          regexStr += '$';

          const regex = new RegExp(regexStr);
          const match = regex.exec(pathname);

          if (match) {
            const params = {};
            for (let i = 0; i < paramNames.length; i++) {
              params[paramNames[i]] = match[i + 1] ?? undefined;
            }
            return { params, query };
          }

          return false;
        }
      },

      /**
       * 创建一个事件总线实例。 (工厂职责)
       * @param {object} logger - 用于错误报告的logger实例。
       */
      createEventBus: (logger) => {
        const listeners = new Map();
        const log = logger || console;

        return {
          on: (eventName, callback) => {
            if (!listeners.has(eventName)) {
              listeners.set(eventName, []);
            }
            listeners.get(eventName).push(callback);
          },
          off: (eventName, callback) => {
            if (listeners.has(eventName)) {
              const eventListeners = listeners.get(eventName);
              const index = eventListeners.indexOf(callback);
              if (index > -1) {
                eventListeners.splice(index, 1);
              }
            }
          },
          emit: (eventName, data) => {
            if (listeners.has(eventName)) {
              [...listeners.get(eventName)].forEach(callback => {
                try {
                  callback(data);
                } catch (e) {
                  log.error(`[EventBus] Error in callback for event "${eventName}":`, e);
                }
              });
            }
          }
        };
      },

      /**
       * 代理 history API 以感知SPA导航。 (配置器职责)
       * @param {object} bus - 事件总线实例。
       */
      patchHistoryForNavigation: (bus, hostWindow) => {
        const history = hostWindow.history;
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function (...args) {
          originalPushState.apply(this, args);
          bus.emit('navigate');
        };

        history.replaceState = function (...args) {
          originalReplaceState.apply(this, args);
          bus.emit('navigate');
        };

        hostWindow.addEventListener('popstate', () => bus.emit('navigate'));
      },

      /**
       * @private
       * [服务工厂] 创建一个通用的DOM净化与安全注入服务。
       * 这是框架中所有CSP/Trusted-Types对抗策略的唯一来源。
       * @param {object} logger - 用于报告错误的 logger 实例。
       * @returns {object} DOMSanitizer 服务实例。
       */
      createDOMSanitizer: (logger) => {
        let _policy = undefined; // 使用闭包缓存 Trusted Types 策略

        const _getPolicy = () => {
          if (_policy === undefined) {
            _policy = null;
            if (window.trustedTypes && window.trustedTypes.createPolicy) {
              try {
                _policy = window.trustedTypes.createPolicy('CaliberUniversalPolicy#html', {
                  createHTML: s => s,
                  createScript: s => s,
                });
              } catch (e) {
                if (window.trustedTypes.defaultPolicy) _policy = window.trustedTypes.defaultPolicy;
              }
            }
          }
          return _policy;
        };

        const service = {
          /**
           * [核心] 创建一个TrustedHTML对象(如果策略可用)。
           * @param {string} htmlString - 要处理的HTML字符串。
           * @returns {TrustedHTML|string} 返回TrustedHTML对象或原始字符串。
           */
          createTrustedHTML(htmlString) {
            const policy = _getPolicy();
            return policy ? policy.createHTML(htmlString) : htmlString;
          },

          /**
           * [便捷方法] 安全地设置一个元素的 innerHTML。
           * @param {Element} element - 目标元素。
           * @param {string} htmlString - 要设置的HTML字符串。
           */
          setInnerHTML(element, htmlString) {
            try {
              element.innerHTML = this.createTrustedHTML(htmlString);
            } catch (e) {
              logger.error('setInnerHTML failed due to CSP.', `Error: ${e.message}`);
            }
          },

          /**
           * [便捷方法] 安全地注入脚本。
           * @param {Document} doc - 目标文档。
           * @param {string} codeString - 脚本字符串。
           */
          injectScript(doc, codeString) {
            try {
              const script = doc.createElement('script');
              const policy = _getPolicy();
              if (policy) script.textContent = policy.createScript(codeString);
              else script.textContent = codeString;
              (doc.head || doc.documentElement).prepend(script);
              script.remove();
            } catch (e) {
              logger.error('Script injection failed due to CSP.', `Error: ${e.message}`);
            }
          },

          /**
           * [便捷方法] 安全地注入样式。
           * @param {Document} doc - 目标文档。
           * @param {string} cssString - 样式字符串。
           * @param {string} id - 样式元素的ID。
           * @returns {HTMLStyleElement|null} 创建的元素或null。
           */
          injectStyle(doc, cssString, id) {
            try {
              const style = doc.createElement('style');
              style.dataset.caliberId = id;
              style.innerHTML = this.createTrustedHTML(cssString);
              doc.head.appendChild(style);
              return style;
            } catch (e) {
              logger.error('Style injection failed due to CSP.', `Error: ${e.message}`);
              return null;
            }
          }
        };

        return service;
      },

      /**
       * 网络请求拦截器服务。
       * 创建一个通用的拦截器服务,它通过安全的脚本注入来代理原生fetch,并使用CustomEvent将响应数据传回沙箱。
       *
       * @param {object} logger - 框架的主 logger 实例。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @param {Document} hostDocument - 宿主页面的 Document 对象。
       * @param {string} safeAppName - 当前应用名称,用于生成唯一的命名空间。
       * @param {boolean} isDebug - 是否为调试模式,用于控制注入脚本的日志输出。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务实例。
       * @returns {FetchInterceptorInstance} 服务对象。   
       */
      createFetchInterceptor: (logger, hostWindow, hostDocument, safeAppName, isDebug, sanitizer) => {
        const namespace = `__CALIBER_${safeAppName}`;
        const patchFlag = `${namespace}_PATCHED`;
        const hooksRegistry = `${namespace}_HOOKS`;
        const responseEventName = `${namespace}_RESPONSE`;

        const responseCallbacks = new Map();
        let listenerRefCount = 0;

        // 事件处理函数
        const _handleInjectedEvent = (event) => {
          const { path, responseData } = event.detail;
          const pathKey = JSON.stringify(path);
          if (responseCallbacks.has(pathKey)) {
            try {
              responseCallbacks.get(pathKey)(responseData);
            } catch (e) {
              logger.error(`[FetchInterceptor] Error in response callback for path [${path.join('/')}]`, e);
            }
          }
        };

        // 统一处理路径验证和转换
        const _validateAndTransformPath = (path, methodName) => {
          if (typeof path === 'string' && path) return [path];
          if (Array.isArray(path) && path.length > 0) return path;
          logger.error(`[FetchInterceptor] ${methodName} failed: path must be a non-empty array or a non-empty string.`);
          return null;
        };

        const service = {
          /**
           * 根据配置对象构建一个 fetch 钩子函数的字符串。
           * 这是一个便捷的“填空题”工具,用于简化 addHook 的使用。
           * @param {object} options - 钩子配置。
           * @param {string} options.targetUrl - 必须完全匹配的目标URL (origin + pathname)。
           * @param {string} [options.method='GET'] - (可选) 匹配的HTTP方法 (大小写不敏感)。
           * @param {string} options.handler - 在匹配成功后,要执行的核心逻辑的函数体字符串。
           *                                   在此字符串中,你可以使用 `urlObject` 和 `config` 这两个变量。
           *                                   它必须返回一个 `{ url: string, config: object }` 或 `undefined`。
           * @returns {string} - 一个完整的、自包含的、可注入的钩子函数字符串。
           */
          createHook({ targetUrl, method = 'GET', handler }) {
            if (!targetUrl || !handler) {
              logger.error(`[FetchInterceptor.createHook] failed: 'targetUrl' and 'handler' are required.`);
              return `() => {}`;
            }
            const template = `
            (url, config) => {
                const TARGET_URL = '${targetUrl}';
                const TARGET_METHOD = '${method.toUpperCase()}';
                try {
                    if (config.method && config.method.toUpperCase() !== TARGET_METHOD) return;
                    const urlObject = new URL(url);
                    if (urlObject.origin + urlObject.pathname !== TARGET_URL) return;
                    
                    const result = (() => { ${handler} })();
                    return result;
                } catch (e) { /* ignore errors */ }
            }`;
            return template;
          },

          /**
           * 添加一个网络请求钩子,并注册一个用于处理响应的回调。
           * @param {string[]|string} path - 钩子的唯一路径。
           * @param {string} hookFunctionString - 修改请求的钩子函数字符串。
           * @param {(responseData: any) => void} responseCallback - 接收响应数据的回调函数。
           */
          addHookWithResponse(path, hookFunctionString, responseCallback) {
            const finalPath = _validateAndTransformPath(path, 'addHookWithResponse');
            if (!finalPath) return;
            if (typeof responseCallback !== 'function') {
              logger.error('[FetchInterceptor] addHookWithResponse failed: responseCallback must be a function.');
              return;
            }

            const pathKey = JSON.stringify(finalPath);
            // 如果是新注册的回调,增加引用计数
            if (!responseCallbacks.has(pathKey)) {
              if (listenerRefCount === 0) {
                hostDocument.addEventListener(responseEventName, _handleInjectedEvent);
                if (isDebug) logger.log(`[FetchInterceptor] Global response listener attached for event: ${responseEventName}`);
              }
              listenerRefCount++;
            }

            responseCallbacks.set(pathKey, responseCallback);
            this.addHook(finalPath, hookFunctionString, true);
          },

          /**
           * 添加或更新一个网络请求钩子。
           * @param {string[]|string} path - 钩子路径。
           * @param {string} hookFunctionString - 钩子函数字符串。
           * @param {boolean} [awaitsResponse=false] - (内部) 标记此钩子是否需要返回响应。
           */
          addHook(path, hookFunctionString, awaitsResponse = false) {
            const finalPath = _validateAndTransformPath(path, 'addHook');
            if (!finalPath || !hookFunctionString) {
              if (!hookFunctionString) logger.error('[FetchInterceptor] addHook failed: hookFunctionString is required.');
              return;
            }

            const pathJson = JSON.stringify(finalPath);
            const injectionCode = `
            (() => {
              const doLog = ${isDebug};
              if (!window['${patchFlag}']) {
                window['${patchFlag}'] = true;
                window['${hooksRegistry}'] = {};
                const originalFetch = window.fetch;
                const RESPONSE_EVENT_NAME = '${responseEventName}';

                const executeHooks = (node, url, config, currentPath = []) => {
                  let matchedPath = null;
                  if (typeof node === 'object' && node !== null) {
                    for (const key in node) {
                      if (Object.prototype.hasOwnProperty.call(node, key)) {
                        const newPath = [...currentPath, key];
                        const hookResult = executeHooks(node[key], url, config, newPath);
                        url = hookResult.url;
                        config = hookResult.config;
                        if (hookResult.matchedPath) {
                            matchedPath = hookResult.matchedPath;
                            break;
                        }
                      }
                    }
                  } else if (typeof node === 'function') {
                    try {
                      const result = node(url, config);
                      if (result && result.url && result.config) {
                        url = result.url;
                        config = result.config;
                        matchedPath = currentPath;
                      }
                    } catch (e) { if (doLog) console.error('[Caliber Hook Error]', e); }
                  }
                  return { url, config, matchedPath };
                };

                window.fetch = async function(...args) {
                  let resource = args[0], config = args[1] || {}, url = new URL(String(resource), window.location.origin).toString();
                  
                  const { url: finalUrl, config: finalConfig, matchedPath } = executeHooks(window['${hooksRegistry}'], url, config);
                  args[0] = finalUrl;
                  args[1] = finalConfig;

                  const response = await originalFetch.apply(this, args);

                  if (matchedPath) {
                    const responseClone = response.clone();
                    responseClone.text().then(text => {
                      let responseData;
                      try {
                        responseData = JSON.parse(text);
                      } catch (e) {
                        responseData = text;
                      }
                      
                      const event = new CustomEvent(RESPONSE_EVENT_NAME, {
                        detail: { path: matchedPath, responseData }
                      });
                      document.dispatchEvent(event);
                    }).catch(e => {
                      if (doLog) console.warn('[Caliber Interceptor] Could not read response body for hooked request.', e);
                    });
                  }
                  
                  return response;
                };
                if (doLog) console.warn('[Caliber] Injected fetch interceptor is active.');
              }

              const setByPath = (obj, p, val) => {
                const last = p.pop();
                let node = obj;
                p.forEach(k => { node = (node[k] = (typeof node[k] === 'object' && node[k] !== null) ? node[k] : {}); });
                node[last] = val;
              };

              setByPath(window['${hooksRegistry}'], ${pathJson}, (${hookFunctionString}));
              if (${isDebug}) console.log(\`[Caliber] Hook at path [${finalPath.join('/')}] added/updated. Awaits response: ${awaitsResponse}\`);
            })();
          `;
            sanitizer.injectScript(hostDocument, injectionCode);
          },

          /**
           * 从注入的钩子注册表中移除一个钩子或分支。
           * @param {string[]|string} path - 要移除的钩子或分支的路径。
           */
          removeHook(path) {
            const finalPath = _validateAndTransformPath(path, 'removeHook');
            if (!finalPath) return;

            const pathKey = JSON.stringify(finalPath);
            // 如果确实存在一个回调被移除,减少引用计数
            if (responseCallbacks.has(pathKey)) {
              responseCallbacks.delete(pathKey);
              listenerRefCount--;

              if (listenerRefCount === 0) {
                hostDocument.removeEventListener(responseEventName, _handleInjectedEvent);
                if (isDebug) logger.log(`[FetchInterceptor] Global response listener removed as no hooks are active.`);
              }
              if (isDebug) logger.log(`[FetchInterceptor] Response callback for path [${finalPath.join('/')}] removed.`);
            }

            const pathJson = JSON.stringify(finalPath);
            const removalCode = `
            (() => {
              const registry = window['${hooksRegistry}'];
              if (!registry) return;
              const path = ${pathJson};
              let parent = registry;
              for (let i = 0; i < path.length - 1; i++) {
                if (typeof parent[path[i]] === 'undefined') return;
                parent = parent[path[i]];
              }
              delete parent[path[path.length - 1]];
              if (${isDebug}) console.log(\`[Caliber] Hook or branch at path [${finalPath.join('/')}] removed.\`);
            })();
          `;
            sanitizer.injectScript(hostDocument, removalCode);
          },

          /**
           * 启动一个链式调用来创建和注册一个拦截器。
           * @param {string|{url: string, method?: string, match?: string|RegExp|Array<string|RegExp>}} urlOrOptions - 目标URL或一个包含URL、方法和页面匹配规则的对象。
           * @returns {object} 一个包含 .onRequest(), .onResponse(), .register() 的构建器对象。
           */
          target(urlOrOptions) {
            const builder = {
              _targetConfig: {},
              _requestHandlerStr: `(url, config) => ({ url, config })`,
              _responseCallback: null,

              _init(targetConfig) {
                this._targetConfig = targetConfig;
                return this;
              },

              /**
               * 定义请求被拦截时要执行的逻辑。
               * @param {string} handlerString - 一个将要被注入的函数体字符串。
               * @returns {builder}
               */
              onRequest(handlerString) {
                this._requestHandlerStr = handlerString;
                return this;
              },

              /**
               * 定义在沙箱中处理响应数据的回调函数。
               * @param {(responseData: any) => void} callback - 回调函数。
               * @returns {builder}
               */
              onResponse(callback) {
                this._responseCallback = callback;
                return this;
              },

              /**
               * 最终确定并注册这个拦截器。
               * @param {string|string[]} id - 拦截器的唯一ID,通常是模块的this.id。
               */
              register(id) {
                const { match } = this._targetConfig;

                const isMatched = _CaliberInternals._checkMatch(match, hostWindow);
                if (!isMatched) {
                  if (isDebug) {
                    logger.log(`[Interceptor.register] Registration for ID '${id}' skipped. Current URL "${hostWindow.location.href}" does not match the rule:`, match);
                  }
                  return;
                }

                if (!id) {
                  logger.error('[Interceptor.register] An ID is required to register a hook.');
                  return;
                }

                const isRequestModified = this._requestHandlerStr !== `(url, config) => ({ url, config })`;
                const isResponseHandled = this._responseCallback !== null;
                if (!isRequestModified && !isResponseHandled) {
                  if (isDebug) {
                    logger.warn(`[Interceptor.register] Registration for ID '${id}' was silently cancelled because both onRequest and onResponse were omitted.`);
                  }
                  return;
                }

                const finalHandler = isRequestModified ? this._requestHandlerStr : `return { url, config };`;

                const fullHookString = service.createHook({
                  targetUrl: this._targetConfig.url,
                  method: this._targetConfig.method,
                  handler: finalHandler
                });

                if (isResponseHandled) {
                  service.addHookWithResponse(id, fullHookString, this._responseCallback);
                } else {
                  service.addHook(id, fullHookString);
                }
              }
            };

            const targetConfig = typeof urlOrOptions === 'string'
              ? { url: urlOrOptions, method: 'GET' }
              : { method: 'GET', ...urlOrOptions };

            return builder._init(targetConfig);
          }
        };
        return service;
      },

      /**
       * @private
       * [原生适配器] 基于Web标准API的默认服务实现。
       */
      _nativeBrowserAdapters: {
        storage: (storageKey) => ({
          get: () => Promise.resolve(JSON.parse(localStorage.getItem(storageKey) || '{}')),
          set: (value) => Promise.resolve(localStorage.setItem(storageKey, JSON.stringify(value))),
        }),
        command: {
          register: (name, callback) => { },
        },
        style: (sanitizer, hostDocument) => ({
          _addedStyles: new Map(),
          add(cssString, id) {
            const styleElement = sanitizer.injectStyle(hostDocument, cssString, id);
            if (styleElement) {
              this._addedStyles.set(id, styleElement);
            }
          },
          remove(id) {
            if (this._addedStyles.has(id)) {
              this._addedStyles.get(id).remove();
              this._addedStyles.delete(id);
            }
          }
        })
      },

      /**
       * @private
       * [服务工厂] 创建一个页面作用域代码执行器服务。
       * 可将任意JS代码字符串注入到宿主页面执行,并异步返回其可序列化的结果。
       * @param {object} logger - 框架的主 logger 实例。
       * @param {string} safeAppName - 应用的安全名称,用于生成唯一事件名。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务实例。
       * @param {Document} hostDocument - 宿主 document 对象。
       * @returns {{execute: (codeString: string) => Promise<any>}} PageScopeExecutor 服务实例。
       */
      createPageScopeExecutor: (logger, safeAppName, sanitizer, hostDocument) => {
        const namespace = `__CALIBER_PAGE_EXECUTOR_${safeAppName}`;

        return {
          async execute(codeString) {
            return new Promise((resolve, reject) => {
              const requestId = `req_${Math.random().toString(36).substr(2, 9)}`;
              const responseEventName = `${namespace}_RESPONSE_${requestId}`;

              const handleResponse = (event) => {
                const { success, data, errorMsg } = event.detail;
                hostDocument.removeEventListener(responseEventName, handleResponse);
                if (success) {
                  resolve(data);
                } else {
                  reject(new Error(errorMsg || 'Page-scope code execution failed.'));
                }
              };

              hostDocument.addEventListener(responseEventName, handleResponse, { once: true });

              const injectionCode = `
              (async () => {
                const RESPONSE_EVENT_NAME = '${responseEventName}';
                try {
                  const result = await (${codeString});
                  
                  document.dispatchEvent(new CustomEvent(RESPONSE_EVENT_NAME, {
                    detail: { success: true, data: result }
                  }));
                } catch (e) {
                  document.dispatchEvent(new CustomEvent(RESPONSE_EVENT_NAME, {
                    detail: { success: false, errorMsg: e.message }
                  }));
                }
              })();
            `;

              sanitizer.injectScript(hostDocument, injectionCode);
            });
          }
        };
      },

    };

    /**
     * 创建并启动一个基于 Caliber 框架的增强脚本应用。
     * 这是 Caliber 框架的唯一入口点。
     * 
     * @param {object} options - 应用的配置对象。
     * @param {string} options.appName - 应用的名称。将用于日志前缀、UI标题和菜单项。
     * @param {class[]} options.modules - 一个由模块类(必须继承自 Caliber.Module)组成的数组。
     * @param {object} options.services - 一个包含所有平台相关服务实现的对象。
     * @param {object} options.services.storage - 存储服务适配器。必须实现 get() 和 set(value) 方法。
     *   @param {() => Promise<object>} options.services.storage.get - 一个异步函数,返回存储的用户配置对象。
     *   @param {(value: object) => Promise<void>} options.services.storage.set - 一个异步函数,将配置对象写入存储。
     * @param {object} options.services.command - 命令服务适配器。必须实现 register(name, callback) 方法。
     *   @param {(name: string, callback: () => void) => void} options.services.command.register - 一个函数,用于注册一个菜单命令。
     * @param {object} [options.services.hostWindow=window] - (可选) 要操作的窗口对象。默认为油猴环境的`unsafeWindow`或标准`window`。
     * @param {object} [options.services.hostDocument=document] - (可选) 要操作的文档对象。默认为`document`。
     * @param {boolean} [options.isDebug=true] - (可选) 是否开启调试模式,会影响日志的输出。默认为`false`。
     * @param {boolean} [options.settingsPanelEnabled=true] - (可选) 设置面板在首次启动时是否默认开启。默认为`true`。
     * @param {object} [options.framework] - (可选) 用于微调 Caliber 框架内部行为的配置。
     * @param {number} [options.framework.domProcessorBatchSize=20] - (可选) 设置 DomBatchProcessor 在每个渲染帧中处理的最大任务数。
     * @returns {Promise<void>}
     */
    async function createApp(options) {
      const { appName, modules, services, isDebug = false, settingsPanelEnabled = true } = options || {};

      // 初始化基础环境
      const loggerFactory = new LoggerService(isDebug);
      const mainLogger = loggerFactory.createMainLogger(appName || 'CaliberApp');
      const hostWindow = (services.hostWindow || (('unsafeWindow' in window) ? unsafeWindow : window));
      const hostDocument = (services.hostDocument || hostWindow.document || document);

      // 创建上下文
      const context = _CaliberInternals._createAppContext(appName);

      // 执行预检 
      if (!_CaliberInternals.runPreflightChecks(options, context.instanceKey, mainLogger, hostWindow)) {
        return;
      }

      const sanitizer = _CaliberInternals.createDOMSanitizer(mainLogger);
      // 优先使用用户提供的,否则使用框架内置的原生适配器
      const finalServices = {
        storage: services.storage || _CaliberInternals._nativeBrowserAdapters.storage(`CALIBER_STORAGE_${appName}`),
        command: _CaliberInternals._nativeBrowserAdapters.command,
        style: services.style || _CaliberInternals._nativeBrowserAdapters.style(sanitizer, hostDocument),
        ...services
      };

      // 初始化核心服务
      const coreServices = _CaliberInternals.initializeCoreServices(options, context, mainLogger, hostWindow, hostDocument, sanitizer);

      // 组装依赖并运行内核
      const injectedServices = {
        hostWindow,
        hostDocument,
        eventBus: coreServices.eventBus,
        storage: finalServices.storage,
        logger: mainLogger,
        style: finalServices.style,
        framework: {
          ...coreServices.framework,
          ...options.framework,
        },
        IS_DEBUG: isDebug,
        APP_NAME: appName,
        SAFE_APP_NAME: context.safeAppName,
        initialConfig: { settingsPanel: { enabled: settingsPanelEnabled } },
      };

      const kernel = new AppKernel(injectedServices);
      hostWindow[context.instanceKey] = kernel;
      modules.forEach(ModuleClass => kernel.registerModule(ModuleClass));
      await kernel.run();

      // 注册外部接口
      finalServices.command.register(`⚙️ ${appName} 设置`, () => {
        coreServices.eventBus.emit('command:toggle-settings-panel');
      });

      mainLogger.log('Bootstrap sequence complete. Application is alive.');
    }

    return {
      createApp,
      Module // 暴露 Module 基类,以便应用脚本可以继承它
    };
  })();

  // 将Caliber对象挂载到window上
  window.Caliber = Caliber;

})((typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window);