Prevent double scaling on home thumbnails

This commit is contained in:
2026-06-01 13:35:45 +08:00
parent faec1140e7
commit ac8e88fe66
2 changed files with 203 additions and 174 deletions

View File

@@ -1,8 +1,8 @@
// ==UserScript==
// @name Chaturbate 缩略图放大 2 倍
// @namespace https://chaturbate.com/
// @version 0.11.0
// @description 放大当前 Chaturbate 房间列表发现页轮播关注下拉与悬停预览缩略图
// @version 0.11.3
// @description 放大当前 Chaturbate 房间列表, 发现页轮播, 关注下拉与悬停预览缩略图
// @match https://chaturbate.com/*
// @match https://*.chaturbate.com/*
// @updateURL http://127.0.0.1:49234/chaturbate-thumbnails-2x.user.js
@@ -14,55 +14,55 @@
// ==/UserScript==
/*
* --- Microsoft Edge脚本完全不生效时可先试 ---
* --- Microsoft Edge: 脚本完全不生效时可先试 ---
*
* 1) 地址栏打开 edge://extensions开启开发人员模式」(Developer mode位置依版本可能在左侧栏或页面底部)。
* 2) 无痕窗口Tampermonkey →「详细信息」→「在 InPrivate 中允许」。
* 3) Tampermonkey 仪表盘确认脚本已启用域名匹配当前站点含 zh-hans 等语言子域)。
* 1) 地址栏打开 edge://extensions, 开启"开发人员模式"(Developer mode; 位置依版本可能在左侧栏或页面底部).
* 2) 无痕窗口: Tampermonkey ->"详细信息"->"在 InPrivate 中允许".
* 3) Tampermonkey 仪表盘确认脚本已启用; 域名匹配当前站点(含 zh-hans 等语言子域).
*/
/*
* 工作原则
* 工作原则:
*
* 1) 只做站点原始尺寸 * 倍率的放大
* 不重写站点原有排版模型不用 object-fit / aspect-ratio / 自定义行高去替代站点布局
* 图片宽高和卡片宽度使用 THUMBNAIL_SCALE卡片整体高度使用 CARD_HEIGHT_SCALE
* 1) 只做"站点原始尺寸 * 倍率"的放大.
* 不重写站点原有排版模型, 不用 object-fit / aspect-ratio / 自定义行高去替代站点布局.
* 图片宽高和卡片宽度使用 THUMBNAIL_SCALE; 卡片整体高度使用 CARD_HEIGHT_SCALE.
*
* 2) 各模块必须自己探测原始值再乘倍率
* 没有探测到原始值就不放大该模块不拿默认尺寸猜页面
* 2) 各模块必须自己探测原始值, 再乘倍率.
* 没有探测到原始值, 就不放大该模块; 不拿默认尺寸猜页面.
*
* 3) 延迟探测不临时禁用样式
* CSS 先注入但所有模块默认关闭等页面原始 DOM 渲染后逐个模块探测原始尺寸成功后才打开该模块
* 模块一旦探测成功就锁定避免后续 mutation 把已经放大的尺寸当成原始值再次相乘
* 3) 延迟探测, 不临时禁用样式.
* CSS 先注入但所有模块默认关闭; 等页面原始 DOM 渲染后, 逐个模块探测原始尺寸, 成功后才打开该模块.
* 模块一旦探测成功就锁定, 避免后续 mutation 把已经放大的尺寸当成原始值再次相乘.
*
* 4) 探测结果缓存的是原始尺寸”,不是放大后的尺寸
* 缓存按模块保存命中缓存就直接设置 CSS 变量并启用模块未命中才延迟探测
* 油猴菜单可清除缓存清除后刷新页面即可重新探测
* 4) 探测结果缓存的是"原始尺寸", 不是放大后的尺寸.
* 缓存按模块保存; 命中缓存就直接设置 CSS 变量并启用模块, 未命中才延迟探测.
* 油猴菜单可清除缓存, 清除后刷新页面即可重新探测.
*
* 5) 顶部关注弹窗的 FOLLOW_DROPDOWN_SHIFT_X 只是位置微调不属于缩略图倍率
* 5) 顶部"关注"弹窗的 FOLLOW_DROPDOWN_SHIFT_X 只是位置微调, 不属于缩略图倍率.
*
* 6) DEBUG 只用于控制台与 window.tmThumbScaleDebug() 诊断不应该弹窗或显示页面调试面板
* 6) DEBUG 只用于控制台与 window.tmThumbScaleDebug() 诊断, 不应该弹窗或显示页面调试面板.
*/
(function () {
"use strict";
// ========== 配置项 ==========
// 图片宽高和卡片宽度倍率2 表示按站点原始尺寸放大到 2 倍
// 图片宽高和卡片宽度倍率: 2 表示按站点原始尺寸放大到 2 倍.
const THUMBNAIL_SCALE = 2;
// 卡片整体高度倍率信息栏边距等不需要和图片一样放大到 2 倍过大会产生空白
// 卡片整体高度倍率: 信息栏, 边距等不需要和图片一样放大到 2 倍, 过大会产生空白.
const CARD_HEIGHT_SCALE = 1.55;
// 顶部关注弹出层水平偏移只调位置不参与缩略图放大倍率
// 顶部"关注"弹出层水平偏移; 只调位置, 不参与缩略图放大倍率.
const FOLLOW_DROPDOWN_SHIFT_X = 10;
// 调试开关控制控制台日志html data-tm-* 标记和 window.tmThumbScaleDebug()
// 调试开关: 控制控制台日志, html data-tm-* 标记和 window.tmThumbScaleDebug().
const DEBUG = true;
// ===========================
const VER = "0.11.0";
// 缓存只保存站点原始尺寸”,不要保存乘过倍率后的尺寸
// 这样以后只改 THUMBNAIL_SCALE / CARD_HEIGHT_SCALE 时可以让缓存自动失效并重新探测避免旧倍率污染新布局
const CACHE_KEY = "tm-thumb-scale:size-cache:v9";
const CACHE_SCHEMA = 9;
const VER = "0.11.3";
// 缓存只保存"站点原始尺寸", 不要保存乘过倍率后的尺寸.
// 这样以后只改 THUMBNAIL_SCALE / CARD_HEIGHT_SCALE 时, 可以让缓存自动失效并重新探测, 避免旧倍率污染新布局.
const CACHE_KEY = "tm-thumb-scale:size-cache:v11";
const CACHE_SCHEMA = 11;
const log = (...args) => {
if (DEBUG) console.log("[tm-thumb-scale]", ...args);
};
@@ -76,43 +76,46 @@
const css = `
/*
* CSS 总开关说明
* CSS 总开关说明:
*
* 下面每一组规则都挂在 html[data-tm-thumb-scale-xxx="1"] 后面
* JS 没有探测到对应模块的原始尺寸前不会设置这个 data 属性
* 这样 CSS 可以提前注入但不会提前改变页面原始布局延迟探测才能读到真实原始值
* 下面每一组规则都挂在 html[data-tm-thumb-scale-xxx="1"] 后面.
* JS 没有探测到对应模块的原始尺寸前, 不会设置这个 data 属性.
* 这样 CSS 可以提前注入, 但不会提前改变页面原始布局, 延迟探测才能读到真实原始值.
*/
/* 首页当前列表 */
/*
* home 模块
* - 适用范围:主页、分类页等普通 #roomlist_root 房间列表
* - 探测值卡片原始宽度。
* - 放大方式:只放大 grid 列宽缩略图保持 100% / auto跟随卡片自然缩放。
* - 注意关注页列表单独用 followingList 模块不和 home 共用缓存
* home 模块:
* - 适用范围: 主页, 分类页等普通 #roomlist_root 房间列表.
* - 探测值: 卡片原始宽高 + 缩略图原始宽高.
* - 放大方式: grid 列宽按 THUMBNAIL_SCALE 放大一次; 缩略图宽度保持 100%; 缩略图高度按 THUMBNAIL_SCALE 放大.
* - 注意: 关注页列表单独用 followingList 模块, 不和 home 共用缓存.
*/
html[data-tm-thumb-scale-home="1"] #main #roomlist_root ul.RoomCardGrid,
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid,
html[data-tm-thumb-scale-home="1"] #main #roomlist_root ul.list:has(li.roomCard),
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) {
/* 改成 grid是为了让放大后的卡片按新宽度自然换行 */
/* 改成 grid, 是为了让放大后的卡片按新宽度自然换行. */
display: grid !important;
/* --tm-thumb-home-width 来自当前页面原始卡片宽度JS 写入时已经乘过倍率 */
/* --tm-thumb-home-width 来自当前页面原始卡片宽度, JS 写入时已经乘过倍率. */
grid-template-columns: repeat(auto-fill, minmax(var(--tm-thumb-home-width), 1fr)) !important;
/* 保留一个稳定间距避免放大后卡片互相贴住 */
/* 保留一个稳定间距, 避免放大后卡片互相贴住. */
gap: 0.6em 0.75em !important;
}
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid > li.RoomCard,
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) li.roomCard {
/* 列宽交给 grid 控制清掉站点原本写死的 li 宽度限制 */
/* 列宽交给 grid 控制, 清掉站点原本写死的 li 宽度限制. */
width: auto !important;
min-width: 0 !important;
max-width: none !important;
height: var(--tm-thumb-home-card-height) !important;
min-height: var(--tm-thumb-home-card-height) !important;
max-height: var(--tm-thumb-home-card-height) !important;
}
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid .RoomCardThumbnail,
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container {
width: 100% !important;
height: auto !important;
height: var(--tm-thumb-home-thumb-height) !important;
display: block !important;
max-width: none !important;
box-sizing: border-box !important;
@@ -121,8 +124,8 @@
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container img,
html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail {
width: 100% !important;
height: auto !important;
/* block 可以消掉图片 inline baseline 带来的细小空隙 */
height: var(--tm-thumb-home-thumb-height) !important;
/* block 可以消掉图片 inline baseline 带来的细小空隙. */
display: block !important;
max-width: none !important;
box-sizing: border-box !important;
@@ -130,13 +133,14 @@
/* 关注页当前列表 */
/*
* followingList 模块
* - 适用范围/followed-cams/ 关注页里的 #roomlist_root 房间列表
* - 为什么和 home 分开两者 DOM 选择器很像但站点可能给不同页面不同原始列宽
* - 缓存也单独存 followingList避免首页探测值污染关注页
* - 和 home 一样只放大 grid 最小列宽;图片跟随卡片自然缩放。
* - 注意:这里仍保留 minmax(..., 1fr),让站点原来的整行拉伸/对齐逻辑继续工作。
* 不能改成固定列宽,否则宽屏下会出现大空洞,看起来比原站更乱。
* followingList 模块:
* - 适用范围: /followed-cams/ 关注页里的 #roomlist_root 房间列表.
* - 为什么和 home 分开: 两者 DOM 选择器很像, 但站点可能给不同页面不同原始列宽.
* - 缓存也单独存 followingList, 避免首页探测值污染关注页.
* - 探测值: 卡片原始宽高 + 缩略图原始宽高.
* - 和 home 一样, grid 列宽按 THUMBNAIL_SCALE 放大一次; 缩略图宽度保持 100%; 缩略图高度按 THUMBNAIL_SCALE 放大.
* - 注意: 这里仍保留 minmax(..., 1fr), 让站点原来的整行拉伸/对齐逻辑继续工作.
* 不能改成固定列宽, 否则宽屏下会出现大空洞, 看起来比原站更乱.
*/
html[data-tm-thumb-scale-following-list="1"] #main #roomlist_root ul.RoomCardGrid,
html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.RoomCardGrid,
@@ -151,11 +155,14 @@
width: auto !important;
min-width: 0 !important;
max-width: none !important;
height: var(--tm-thumb-following-list-card-height) !important;
min-height: var(--tm-thumb-following-list-card-height) !important;
max-height: var(--tm-thumb-following-list-card-height) !important;
}
html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.RoomCardGrid .RoomCardThumbnail,
html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container {
width: 100% !important;
height: auto !important;
height: var(--tm-thumb-following-list-thumb-height) !important;
display: block !important;
max-width: none !important;
box-sizing: border-box !important;
@@ -164,17 +171,17 @@
html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container img,
html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail {
width: 100% !important;
height: auto !important;
height: var(--tm-thumb-following-list-thumb-height) !important;
display: block !important;
max-width: none !important;
box-sizing: border-box !important;
}
/* 直播间页底部更多这样的直播间 */
/* 直播间页底部"更多这样的直播间" */
/*
* related 模块
* - 适用范围直播间页面底部更多这样的房间”。
* - 探测值卡片原始宽高 + 缩略图原始宽高
* - 放大方式卡片和缩略图分别按自己的原始宽高乘倍率避免把 card 宽度误当 thumb 宽度
* related 模块:
* - 适用范围: 直播间页面底部"更多这样的房间".
* - 探测值: 卡片原始宽高 + 缩略图原始宽高.
* - 放大方式: 卡片和缩略图分别按自己的原始宽高乘倍率, 避免把 card 宽度误当 thumb 宽度.
*/
html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.RoomCardGrid,
html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard),
@@ -189,7 +196,7 @@
width: auto !important;
min-width: 0 !important;
max-width: none !important;
/* 卡片整体高度也要放大只放大缩略图高度时下方用户名/人数栏可能被压住 */
/* 卡片整体高度也要放大; 只放大缩略图高度时, 下方用户名/人数栏可能被压住. */
height: var(--tm-thumb-related-card-height) !important;
min-height: var(--tm-thumb-related-card-height) !important;
max-height: var(--tm-thumb-related-card-height) !important;
@@ -197,9 +204,9 @@
html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.RoomCardGrid .RoomCardThumbnail,
html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard) .room_thumbnail_container,
html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container {
/* --tm-thumb-related-thumb-width 来自原始缩略图宽度不直接复用卡片宽度 */
/* --tm-thumb-related-thumb-width 来自原始缩略图宽度, 不直接复用卡片宽度. */
width: var(--tm-thumb-related-thumb-width) !important;
/* --tm-thumb-related-height 来自原始缩略图容器高度 */
/* --tm-thumb-related-height 来自原始缩略图容器高度. */
height: var(--tm-thumb-related-height) !important;
display: block !important;
max-width: none !important;
@@ -210,24 +217,24 @@
html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard) .room_thumbnail,
html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container img,
html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail {
/* 图片宽度也按原始缩略图宽度放大避免卡片边框/间隙被算进图片宽度 */
/* 图片宽度也按原始缩略图宽度放大, 避免卡片边框/间隙被算进图片宽度. */
width: var(--tm-thumb-related-thumb-width) !important;
/* 图片和容器使用同一个高度避免图片溢出或下方信息栏错位 */
/* 图片和容器使用同一个高度, 避免图片溢出或下方信息栏错位. */
height: var(--tm-thumb-related-height) !important;
display: block !important;
max-width: none !important;
box-sizing: border-box !important;
}
/* 顶部关注下拉 */
/* 顶部"关注"下拉 */
/*
* follow 模块
* - 适用范围顶部关注菜单弹出的房间列表
* - 探测值弹窗中单个房间卡片宽高 + 图片宽高
* - 放大方式卡片和图片各按自己的原始宽高乘倍率弹窗总宽度按 2 列计算
* follow 模块:
* - 适用范围: 顶部"关注"菜单弹出的房间列表.
* - 探测值: 弹窗中单个房间卡片宽高 + 图片宽高.
* - 放大方式: 卡片和图片各按自己的原始宽高乘倍率; 弹窗总宽度按 2 列计算.
*/
html[data-tm-thumb-scale-follow="1"] .FollowedDropdown {
/* 96vw 防止弹窗在窄窗口里横向溢出 */
/* 96vw 防止弹窗在窄窗口里横向溢出. */
width: min(96vw, var(--tm-thumb-follow-width)) !important;
min-width: min(96vw, var(--tm-thumb-follow-width)) !important;
max-width: min(96vw, var(--tm-thumb-follow-width)) !important;
@@ -241,7 +248,7 @@
}
html[data-tm-thumb-scale-follow="1"] .FollowedDropdown__rooms {
display: grid !important;
/* 当前设计保持两列列宽来自原始卡片宽度 * 倍率 */
/* 当前设计保持两列, 列宽来自原始卡片宽度 * 倍率. */
grid-template-columns: repeat(2, minmax(var(--tm-thumb-follow-card-width), 1fr)) !important;
grid-gap: 0.6em 0.75em !important;
}
@@ -266,43 +273,43 @@
box-sizing: border-box !important;
}
html[data-tm-thumb-scale-follow="1"] .HeaderNavBar__following .FollowedDropdown {
/* 只做用户要求的右移微调不参与缩略图尺寸计算 */
/* 只做用户要求的右移微调, 不参与缩略图尺寸计算. */
transform: translateX(${FOLLOW_DROPDOWN_SHIFT_X}px) !important;
}
/*
* 悬停关注星星等出现的浮层预览多为 react-tooltipportal 挂在 body
* 仅放大直播间缩略图域名避免误伤图标类小图
* 悬停"关注"星星等出现的浮层预览(多为 react-tooltip, portal 挂在 body)
* 仅放大直播间缩略图域名, 避免误伤图标类小图
*/
html[data-tm-thumb-scale-tooltip="1"] body > div[id^="react-tooltip"] img[src*="live.mmcdn.com"],
html[data-tm-thumb-scale-tooltip="1"] body > div[id^="react-tooltip"] img[src*="thumb.live.mmcdn"],
html[data-tm-thumb-scale-tooltip="1"] [data-floating-ui-portal] img[src*="live.mmcdn.com"] {
/* tooltip 模块只改直播缩略图域名避免误伤页面里的小图标 */
/* tooltip 模块只改直播缩略图域名, 避免误伤页面里的小图标. */
width: var(--tm-thumb-tooltip-width) !important;
max-width: min(96vw, var(--tm-thumb-tooltip-width)) !important;
height: var(--tm-thumb-tooltip-height) !important;
display: block !important;
box-sizing: border-box !important;
}
/* 发现页轮播缩略图 + 容器高度 */
/* 发现页: 轮播缩略图 + 容器高度 */
/*
* 发现页模块
* - 适用范围发现页轮播比如最受欢迎”。
* - 探测值
* 1. li 卡片宽高
* 2. 缩略图容器高度
* 3. triple/double/single 三种轮播 ul 高度
* 4. 对应箭头容器高度
* - 放大方式图片宽高和卡片宽度乘 THUMBNAIL_SCALE卡片高度轮播容器高度箭头高度乘 CARD_HEIGHT_SCALE
* - 启用条件全部值都探测到才打开发现页放大缺一项就不动避免局部放大导致错位
* 发现页模块:
* - 适用范围: 发现页轮播, 比如"最受欢迎".
* - 探测值:
* 1. li 卡片宽高;
* 2. 缩略图容器高度;
* 3. triple/double/single 三种轮播 ul 高度;
* 4. 对应箭头容器高度.
* - 放大方式: 图片宽高和卡片宽度乘 THUMBNAIL_SCALE; 卡片高度, 轮播容器高度, 箭头高度乘 CARD_HEIGHT_SCALE.
* - 启用条件: 全部值都探测到才打开发现页放大, 缺一项就不动, 避免局部放大导致错位.
*/
html[data-tm-thumb-scale-discover="1"] .carousel-root .triple-rows ul.list,
html[data-tm-thumb-scale-discover="1"] #discover_root .triple-rows ul.list {
/* 三行轮播的整体高度按 CARD_HEIGHT_SCALE 放大避免卡片下方出现过多空白 */
/* 三行轮播的整体高度按 CARD_HEIGHT_SCALE 放大, 避免卡片下方出现过多空白. */
height: var(--tm-thumb-discover-triple-ul) !important;
}
html[data-tm-thumb-scale-discover="1"] .carousel-root .triple-rows .carousel-arrow-container,
html[data-tm-thumb-scale-discover="1"] #discover_root .triple-rows .carousel-arrow-container {
/* 箭头容器也按 CARD_HEIGHT_SCALE 放大否则右侧箭头会和轮播高度不一致 */
/* 箭头容器也按 CARD_HEIGHT_SCALE 放大, 否则右侧箭头会和轮播高度不一致. */
height: var(--tm-thumb-discover-triple-arrow) !important;
}
html[data-tm-thumb-scale-discover="1"] .carousel-root .double-rows ul.list,
@@ -323,15 +330,15 @@
}
html[data-tm-thumb-scale-discover="1"] .carousel-root .room-list-carousel ul.list > li,
html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel ul.list > li {
/* 卡片宽度来自原始 li 宽度例如日志里出现过 182 -> 364 */
/* 卡片宽度来自原始 li 宽度, 例如日志里出现过 182 -> 364. */
width: var(--tm-thumb-discover-width) !important;
min-width: var(--tm-thumb-discover-width) !important;
max-width: var(--tm-thumb-discover-width) !important;
/* 卡片整体高度也必须放大否则用户名/人数信息区会压住下一行 */
/* 卡片整体高度也必须放大, 否则用户名/人数信息区会压住下一行. */
height: var(--tm-thumb-discover-card-height) !important;
min-height: var(--tm-thumb-discover-card-height) !important;
max-height: var(--tm-thumb-discover-card-height) !important;
/* flex-basis 和 width 保持一致轮播横向滑动距离才稳定 */
/* flex-basis 和 width 保持一致, 轮播横向滑动距离才稳定. */
flex: 0 0 var(--tm-thumb-discover-width) !important;
box-sizing: border-box !important;
vertical-align: top !important;
@@ -348,7 +355,7 @@
html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel .room_thumbnail_container img,
html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel .room_thumbnail {
width: var(--tm-thumb-discover-thumb-width) !important;
/* 缩略图高度来自原始 thumb 高度例如 101 -> 202 */
/* 缩略图高度来自原始 thumb 高度, 例如 101 -> 202. */
height: var(--tm-thumb-discover-height) !important;
display: block !important;
max-width: none !important;
@@ -357,23 +364,23 @@
`;
const STYLE_ID = "tm-thumb-scale-style";
// 只观察一次 head避免重复创建 MutationObserver
// 只观察一次 head, 避免重复创建 MutationObserver.
let observerStarted = false;
// 只观察一次 body页面内容变化时用于捕捉动态弹窗/tooltip
// 只观察一次 body, 页面内容变化时用于捕捉动态弹窗/tooltip.
let pageObserverStarted = false;
// 探测防抖计时器mutation 很多时只执行最后一次探测
// 探测防抖计时器; mutation 很多时只执行最后一次探测.
let measureTimer = null;
// 记录样式注入次数主要用于调试确认 GM_addStyle 是否真的执行
// 记录样式注入次数, 主要用于调试确认 GM_addStyle 是否真的执行.
let injectCount = 0;
// 记录最后一次注入方式GM_addStyle手动 style失败等
// 记录最后一次注入方式: GM_addStyle, 手动 style, 失败等.
let lastInjectMethod = "none";
// GM_addStyle 在某些环境不会返回可查询的 style 节点所以单独记录逻辑状态
// GM_addStyle 在某些环境不会返回可查询的 style 节点, 所以单独记录逻辑状态.
let styleInjected = false;
// 最近一次 window.tmThumbScaleDebug() 返回的数据
// 最近一次 window.tmThumbScaleDebug() 返回的数据.
let lastReport = null;
// 最近一次尺寸探测结果控制台里排错主要看这个
// 最近一次尺寸探测结果, 控制台里排错主要看这个.
let lastMeasure = null;
// 模块锁某模块一旦成功应用缓存或探测值就不再重新探测
// 模块锁: 某模块一旦成功应用缓存或探测值, 就不再重新探测.
const moduleReady = {
home: false,
followingList: false,
@@ -383,7 +390,7 @@
discover: false,
};
const setDebugAttr = (name, value) => {
// 调试属性写到 html 上方便在 Elements 面板确认脚本是否运行
// 调试属性写到 html 上, 方便在 Elements 面板确认脚本是否运行.
if (!DEBUG) return;
try {
document.documentElement.setAttribute(`data-tm-thumb-scale-${name}`, String(value));
@@ -405,14 +412,14 @@
return false;
}
};
// 所有 CSS 变量都在这里统一乘倍率调用方传入的必须是页面原始尺寸
// 所有 CSS 变量都在这里统一乘倍率.调用方传入的必须是页面原始尺寸.
const setVar = (name, value) => {
const style = rootStyle();
if (!style || !Number.isFinite(value) || value <= 0) return false;
style.setProperty(name, scaledPx(value));
return true;
};
// 只给 card 整体高度使用图片宽高仍然走 setVar()继续乘 THUMBNAIL_SCALE
// 只给 card 整体高度使用.图片宽高仍然走 setVar(), 继续乘 THUMBNAIL_SCALE.
const setCardHeightVar = (name, value) => {
const style = rootStyle();
if (!style || !Number.isFinite(value) || value <= 0) return false;
@@ -425,8 +432,8 @@
style.setProperty(name, scaledCardHeightFromThumbPx(cardHeight, thumbHeight));
return true;
};
// 每个模块的 CSS 都由 html[data-tm-thumb-scale-模块名="1"] 控制
// 未探测到原始值时不设置开关对应模块完全不放大避免用错误尺寸猜页面
// 每个模块的 CSS 都由 html[data-tm-thumb-scale-模块名="1"] 控制.
// 未探测到原始值时不设置开关, 对应模块完全不放大, 避免用错误尺寸猜页面.
const setModuleReady = (name, ready) => {
try {
const attrName = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
@@ -436,7 +443,7 @@
} catch (_) {}
};
const isRect = (value) => (
// 缓存读取时做最低限度校验防止坏数据进入 CSS 变量
// 缓存读取时做最低限度校验, 防止坏数据进入 CSS 变量.
value
&& Number.isFinite(value.width)
&& Number.isFinite(value.height)
@@ -444,15 +451,15 @@
&& value.height > 0
);
const cloneRect = (value) => (isRect(value) ? {
// 缓存不需要亚像素无限精度保留两位小数足够复现布局
// 缓存不需要亚像素无限精度, 保留两位小数足够复现布局.
width: Math.round(value.width * 100) / 100,
height: Math.round(value.height * 100) / 100,
} : null);
const readCache = () => {
try {
// localStorage 可能被浏览器策略禁用所以所有缓存操作都要 try/catch
// localStorage 可能被浏览器策略禁用, 所以所有缓存操作都要 try/catch.
if (typeof localStorage === "undefined") return null;
// 缓存格式错误倍率不同结构版本不同都视为无缓存
// 缓存格式错误, 倍率不同, 结构版本不同, 都视为无缓存.
const parsed = JSON.parse(localStorage.getItem(CACHE_KEY) || "null");
if (
!parsed
@@ -468,7 +475,7 @@
};
const writeCache = (cache) => {
try {
// 写入失败不影响脚本运行只是下次还会重新探测
// 写入失败不影响脚本运行, 只是下次还会重新探测.
if (typeof localStorage === "undefined") return false;
localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
return true;
@@ -477,8 +484,8 @@
}
};
const updateCacheModule = (name, data) => {
// 模块级缓存发现页、首页、关注下拉等互不影响
// 只更新当前模块保留其它模块已经探测好的原始尺寸
// 模块级缓存: 发现页, 首页, 关注下拉等互不影响.
// 只更新当前模块, 保留其它模块已经探测好的原始尺寸.
const cache = readCache() || {
schema: CACHE_SCHEMA,
scale: THUMBNAIL_SCALE,
@@ -492,14 +499,14 @@
};
const clearCache = () => {
try {
// 油猴菜单调用这里清完后不强制刷新由用户自己刷新页面重新探测
// 油猴菜单调用这里; 清完后不强制刷新, 由用户自己刷新页面重新探测.
if (typeof localStorage !== "undefined") localStorage.removeItem(CACHE_KEY);
} catch (_) {}
log("cache cleared; refresh page to detect sizes again");
};
const registerMenu = () => {
try {
// GM_registerMenuCommand 需要在 header 里 @grant没有该 API 时静默跳过
// GM_registerMenuCommand 需要在 header 里 @grant; 没有该 API 时静默跳过.
if (typeof GM_registerMenuCommand === "function") {
GM_registerMenuCommand("清除 Chaturbate 缩略图探测缓存", clearCache);
}
@@ -508,12 +515,20 @@
const applyCachedModule = (name, data) => {
if (!data || moduleReady[name]) return false;
// 缓存命中时直接写 CSS 变量并打开模块开关
// 这里仍然通过 setVar() 乘倍率因此缓存中始终保持原始尺寸
if (name === "home" && !isFollowingListPage() && isRect(data.homeCard)) {
moduleReady.home = setVar("--tm-thumb-home-width", data.homeCard.width);
} else if (name === "followingList" && isFollowingListPage() && isRect(data.followingListCard)) {
moduleReady.followingList = setVar("--tm-thumb-following-list-width", data.followingListCard.width);
// 缓存命中时直接写 CSS 变量并打开模块开关.
// 这里仍然通过 setVar() 乘倍率, 因此缓存中始终保持原始尺寸.
if (name === "home" && !isFollowingListPage() && isRect(data.homeCard) && isRect(data.homeThumb)) {
moduleReady.home = [
setVar("--tm-thumb-home-width", data.homeCard.width),
setCardHeightFromThumbVar("--tm-thumb-home-card-height", data.homeCard.height, data.homeThumb.height),
setVar("--tm-thumb-home-thumb-height", data.homeThumb.height),
].every(Boolean);
} else if (name === "followingList" && isFollowingListPage() && isRect(data.followingListCard) && isRect(data.followingListThumb)) {
moduleReady.followingList = [
setVar("--tm-thumb-following-list-width", data.followingListCard.width),
setCardHeightFromThumbVar("--tm-thumb-following-list-card-height", data.followingListCard.height, data.followingListThumb.height),
setVar("--tm-thumb-following-list-thumb-height", data.followingListThumb.height),
].every(Boolean);
} else if (name === "related" && isRect(data.relatedCard) && isRect(data.relatedThumb)) {
const widthReady = setVar("--tm-thumb-related-width", data.relatedCard.width);
const cardHeightReady = setCardHeightVar("--tm-thumb-related-card-height", data.relatedCard.height);
@@ -582,18 +597,18 @@
return Object.values(applied).some(Boolean);
};
const rectOf = (el) => {
// getBoundingClientRect 能拿到 CSS 布局后的实际渲染尺寸
// getBoundingClientRect 能拿到 CSS 布局后的实际渲染尺寸.
if (!el || !el.getBoundingClientRect) return null;
const rect = el.getBoundingClientRect();
const width = rect.width || el.offsetWidth || 0;
const height = rect.height || el.offsetHeight || 0;
// 太小的元素通常是图标占位或尚未布局完成的节点不作为缩略图基准
// 太小的元素通常是图标, 占位或尚未布局完成的节点, 不作为缩略图基准.
if (width < 20 && height < 20) return null;
return { width, height };
};
const firstRect = (selector) => {
try {
// 同一类列表里取第一个有尺寸的节点即可因为同模块卡片原始尺寸应一致
// 同一类列表里取第一个有尺寸的节点即可, 因为同模块卡片原始尺寸应一致.
if (!document.querySelectorAll) return null;
const nodes = document.querySelectorAll(selector);
for (const node of nodes) {
@@ -605,29 +620,31 @@
};
const detectAndApplySizes = (stage = "detect") => {
/*
* 探测策略
* 探测策略:
*
* 1) CSS 已经注入但各模块默认关闭所以页面先按站点原始布局渲染
* 2) 延迟/MutationObserver 触发后读取还没 ready 的模块尺寸
* 3) 某个模块探测成功后写入 CSS 变量打开模块开关写入缓存并把 moduleReady 锁住
* 4) 锁住后的模块后续不再探测避免把已放大的尺寸再次当成原始尺寸
* 1) CSS 已经注入, 但各模块默认关闭, 所以页面先按站点原始布局渲染.
* 2) 延迟/MutationObserver 触发后, 读取还没 ready 的模块尺寸.
* 3) 某个模块探测成功后, 写入 CSS 变量, 打开模块开关, 写入缓存, 并把 moduleReady 锁住.
* 4) 锁住后的模块后续不再探测, 避免把已放大的尺寸再次当成原始尺寸.
*/
const followingListPage = isFollowingListPage();
const measured = {
// 首页普通房间列表:只探测卡片宽度。缩略图跟随卡片自然缩放,避免重复放大。
// 首页普通房间列表: 卡片宽高 + 缩略图宽高四项独立探测.
homeCard: moduleReady.home || followingListPage ? null : firstRect("#roomlist_root li.RoomCard, #roomlist_root li.roomCard"),
// 关注页房间列表:只探测卡片宽度;独立缓存,避免和首页混用。
homeThumb: moduleReady.home || followingListPage ? null : firstRect("#roomlist_root .RoomCardThumbnail, #roomlist_root .room_thumbnail_container"),
// 关注页房间列表: 选择器和首页相似, 但独立探测, 独立缓存, 独立 CSS 变量.
followingListCard: moduleReady.followingList || !followingListPage ? null : firstRect("#roomlist_root li.RoomCard, #roomlist_root li.roomCard"),
// 频道页“更多这样的房间”:卡片宽高和缩略图宽高都要探测,否则放大后行高容易不齐。
followingListThumb: moduleReady.followingList || !followingListPage ? null : firstRect("#roomlist_root .RoomCardThumbnail, #roomlist_root .room_thumbnail_container"),
// 频道页"更多这样的房间": 卡片宽高和缩略图宽高都要探测, 否则放大后行高容易不齐.
relatedCard: moduleReady.related ? null : firstRect(".BaseRoomContents ul.RoomCardGrid > li.RoomCard, #main.roomPage ul.list:has(li.roomCard) li.roomCard, .BaseRoomContents ul.list:has(li.roomCard) li.roomCard"),
relatedThumb: moduleReady.related ? null : firstRect(".BaseRoomContents ul.RoomCardGrid .RoomCardThumbnail, #main.roomPage ul.list:has(li.roomCard) .room_thumbnail_container, .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container"),
// 顶部关注弹窗通常弹出后才有 DOM靠 mutation 触发探测
// 顶部"关注"弹窗: 通常弹出后才有 DOM, 靠 mutation 触发探测.
followCard: moduleReady.follow ? null : firstRect(".FollowedDropdown__room, .FollowedDropdown__room-image"),
followThumb: moduleReady.follow ? null : firstRect(".FollowedDropdown__room-image"),
// 悬浮预览tooltip 是 portal 动态插入也靠 mutation 捕捉
// 悬浮预览: tooltip 是 portal 动态插入, 也靠 mutation 捕捉.
tooltipThumb: moduleReady.tooltip ? null : firstRect('body > div[id^="react-tooltip"] img[src*="live.mmcdn.com"], body > div[id^="react-tooltip"] img[src*="thumb.live.mmcdn"], [data-floating-ui-portal] img[src*="live.mmcdn.com"]'),
// 发现页 carousel必须同时探测 cardthumb行容器和箭头高度全部成功后才启用
// 这样最受欢迎等多行轮播能保持站点原来的对齐关系只是整体按倍率放大
// 发现页 carousel: 必须同时探测 card, thumb, 行容器和箭头高度, 全部成功后才启用.
// 这样"最受欢迎"等多行轮播能保持站点原来的对齐关系, 只是整体按倍率放大.
discoverCard: moduleReady.discover ? null : firstRect(".carousel-root .room-list-carousel ul.list > li, #discover_root .room-list-carousel ul.list > li"),
discoverThumb: moduleReady.discover ? null : firstRect(".carousel-root .room-list-carousel .room_thumbnail_container, #discover_root .room-list-carousel .room_thumbnail_container"),
discoverTripleUl: moduleReady.discover ? null : firstRect(".carousel-root .triple-rows ul.list, #discover_root .triple-rows ul.list"),
@@ -639,29 +656,39 @@
};
if (!moduleReady.home && !followingListPage) {
// home 依赖卡片宽度;成功后立刻缓存后续普通列表页面可直接套用
moduleReady.home = setVar("--tm-thumb-home-width", measured.homeCard && measured.homeCard.width);
// home 依赖 card/thumb 各自宽高; 成功后立刻缓存, 后续普通列表页面可直接套用.
moduleReady.home = [
setVar("--tm-thumb-home-width", measured.homeCard && measured.homeCard.width),
setCardHeightFromThumbVar("--tm-thumb-home-card-height", measured.homeCard && measured.homeCard.height, measured.homeThumb && measured.homeThumb.height),
setVar("--tm-thumb-home-thumb-height", measured.homeThumb && measured.homeThumb.height),
].every(Boolean);
setModuleReady("home", moduleReady.home);
if (moduleReady.home) {
updateCacheModule("home", {
homeCard: cloneRect(measured.homeCard),
homeThumb: cloneRect(measured.homeThumb),
});
}
}
if (!moduleReady.followingList && followingListPage) {
// followingList 单独缓存避免关注页拿到首页的原始宽度
moduleReady.followingList = setVar("--tm-thumb-following-list-width", measured.followingListCard && measured.followingListCard.width);
// followingList 单独缓存, 避免关注页拿到首页的原始宽度.
moduleReady.followingList = [
setVar("--tm-thumb-following-list-width", measured.followingListCard && measured.followingListCard.width),
setCardHeightFromThumbVar("--tm-thumb-following-list-card-height", measured.followingListCard && measured.followingListCard.height, measured.followingListThumb && measured.followingListThumb.height),
setVar("--tm-thumb-following-list-thumb-height", measured.followingListThumb && measured.followingListThumb.height),
].every(Boolean);
setModuleReady("followingList", moduleReady.followingList);
if (moduleReady.followingList) {
updateCacheModule("followingList", {
followingListCard: cloneRect(measured.followingListCard),
followingListThumb: cloneRect(measured.followingListThumb),
});
}
}
if (!moduleReady.related) {
// related 同时依赖卡片宽度卡片高度缩略图宽度和缩略图高度四者缺一不可
// related 同时依赖卡片宽度, 卡片高度, 缩略图宽度和缩略图高度; 四者缺一不可.
const relatedWidthReady = setVar("--tm-thumb-related-width", measured.relatedCard && measured.relatedCard.width);
const relatedCardHeightReady = setCardHeightVar("--tm-thumb-related-card-height", measured.relatedCard && measured.relatedCard.height);
const relatedThumbWidthReady = setVar("--tm-thumb-related-thumb-width", measured.relatedThumb && measured.relatedThumb.width);
@@ -677,7 +704,7 @@
}
if (!moduleReady.follow) {
// follow 的总弹窗宽度按两列计算单卡原始宽度 * 倍率 * 2 + 2em 间距余量
// follow 的总弹窗宽度按两列计算: 单卡原始宽度 * 倍率 * 2 + 2em 间距余量.
const followReady = [
setVar("--tm-thumb-follow-card-width", measured.followCard && measured.followCard.width),
setCardHeightFromThumbVar("--tm-thumb-follow-card-height", measured.followCard && measured.followCard.height, measured.followThumb && measured.followThumb.height),
@@ -699,7 +726,7 @@
}
if (!moduleReady.tooltip) {
// tooltip 出现时机不固定所以通常由 body mutation 触发探测
// tooltip 出现时机不固定, 所以通常由 body mutation 触发探测.
moduleReady.tooltip = [
setVar("--tm-thumb-tooltip-width", measured.tooltipThumb && measured.tooltipThumb.width),
setVar("--tm-thumb-tooltip-height", measured.tooltipThumb && measured.tooltipThumb.height),
@@ -711,7 +738,7 @@
}
if (!moduleReady.discover) {
// 发现页要求更严格少一个高度都不开启宁可不放大也不要错位
// 发现页要求更严格: 少一个高度都不开启, 宁可不放大, 也不要错位.
const discoverWidthReady = setVar("--tm-thumb-discover-width", measured.discoverCard && measured.discoverCard.width);
const discoverCardHeightReady = setCardHeightVar("--tm-thumb-discover-card-height", measured.discoverCard && measured.discoverCard.height);
const discoverThumbWidthReady = setVar("--tm-thumb-discover-thumb-width", measured.discoverThumb && measured.discoverThumb.width);
@@ -746,7 +773,7 @@
return lastMeasure;
};
const scheduleMeasure = (stage, delay = 0) => {
// 所有探测都通过这里排队避免 React 连续插入节点时频繁测量布局
// 所有探测都通过这里排队, 避免 React 连续插入节点时频繁测量布局.
if (typeof setTimeout !== "function") {
detectAndApplySizes(stage);
return;
@@ -760,7 +787,7 @@
const selectorCount = (selector) => {
try {
// 诊断用计数selector 不被浏览器支持时返回错误字符串而不是中断脚本
// 诊断用计数; selector 不被浏览器支持时返回错误字符串而不是中断脚本.
if (!document.querySelectorAll) return -1;
return document.querySelectorAll(selector).length;
} catch (e) {
@@ -770,7 +797,7 @@
const computedInfo = (selector) => {
try {
// 诊断用输出关键容器的 display/grid/width方便在控制台判断 CSS 是否命中
// 诊断用: 输出关键容器的 display/grid/width, 方便在控制台判断 CSS 是否命中.
if (!document.querySelector || typeof getComputedStyle !== "function") return null;
const el = document.querySelector(selector);
if (!el) return null;
@@ -791,7 +818,7 @@
};
const reportDiagnostics = (stage) => {
// 统一收集调试信息手动执行 window.tmThumbScaleDebug() 也会走这里
// 统一收集调试信息, 手动执行 window.tmThumbScaleDebug() 也会走这里.
const styleNodePresent = !!document.getElementById(STYLE_ID);
const stylePresent = styleInjected || styleNodePresent;
const counts = {
@@ -869,13 +896,13 @@
lastReport = report;
if (typeof window !== "undefined") {
// 暴露在页面 window方便控制台直接查看最近一次报告
// 暴露在页面 window, 方便控制台直接查看最近一次报告.
try {
window.tmThumbScaleLastReport = report;
} catch (_) {}
}
if (typeof unsafeWindow !== "undefined") {
// Tampermonkey 隔离环境下unsafeWindow 能把 API 暴露给页面上下文
// Tampermonkey 隔离环境下, unsafeWindow 能把 API 暴露给页面上下文.
try {
unsafeWindow.tmThumbScaleLastReport = report;
} catch (_) {}
@@ -895,7 +922,7 @@
};
const exposeDebugApi = () => {
// 手动调试入口控制台执行 tmThumbScaleDebug() 可立即生成报告
// 手动调试入口: 控制台执行 tmThumbScaleDebug() 可立即生成报告.
const api = (stage = "manual") => reportDiagnostics(stage);
const targets = [];
if (typeof window !== "undefined") targets.push(window);
@@ -910,7 +937,7 @@
};
const inject = () => {
// 注入 CSS 前先处理旧版本 style避免更新脚本后新旧规则同时存在
// 注入 CSS 前先处理旧版本 style, 避免更新脚本后新旧规则同时存在.
const existingStyle = document.getElementById(STYLE_ID);
if (existingStyle) {
const existingVer = existingStyle.getAttribute("data-tm-thumb-scale-ver");
@@ -930,7 +957,7 @@
try {
if (typeof GM_addStyle === "function") {
// Tampermonkey 推荐使用 GM_addStyle比手动 append style 更稳定
// Tampermonkey 推荐使用 GM_addStyle, 比手动 append style 更稳定.
const style = GM_addStyle(css);
if (style) {
style.id = STYLE_ID;
@@ -945,7 +972,7 @@
}
const style = document.createElement("style");
// GM_addStyle 不可用时才走手动 style 兜底
// GM_addStyle 不可用时才走手动 style 兜底.
style.id = STYLE_ID;
style.setAttribute("data-tm-thumb-scale-ver", VER);
style.textContent = css;
@@ -968,7 +995,7 @@
};
const startHeadObserver = () => {
// 有些 SPA/扩展会移除 head 里的 style这里只负责把本脚本样式补回去
// 有些 SPA/扩展会移除 head 里的 style, 这里只负责把本脚本样式补回去.
if (!document.head || observerStarted) return;
observerStarted = true;
const mo = new MutationObserver(() => {
@@ -981,8 +1008,8 @@
};
const startPageObserver = () => {
// Chaturbate 很多内容是 React 动态插入的尤其关注下拉和 tooltip
// 观察 body 后每次 DOM 变化都延迟探测一次尚未 ready 的模块
// Chaturbate 很多内容是 React 动态插入的, 尤其关注下拉和 tooltip.
// 观察 body 后, 每次 DOM 变化都延迟探测一次尚未 ready 的模块.
if (!document.body || pageObserverStarted || typeof MutationObserver !== "function") return;
pageObserverStarted = true;
const mo = new MutationObserver(() => scheduleMeasure("mutation", 300));
@@ -990,30 +1017,30 @@
};
log("start v" + VER);
// 注册油猴菜单用户需要重测布局时可从菜单清除 localStorage 缓存
// 注册油猴菜单; 用户需要重测布局时, 可从菜单清除 localStorage 缓存.
registerMenu();
// 尽早尝试应用缓存缓存命中的模块会立即设置 CSS 变量和 data 开关
// 尽早尝试应用缓存.缓存命中的模块会立即设置 CSS 变量和 data 开关.
applyCachedSizes();
// 暴露调试 API方便后续在控制台手动检查
// 暴露调试 API, 方便后续在控制台手动检查.
exposeDebugApi();
// 第一次报告记录启动时是否有缓存CSS 变量是否已经设置
// 第一次报告: 记录启动时是否有缓存, CSS 变量是否已经设置.
reportDiagnostics("start-before-inject");
// 注入 CSS因为模块默认关闭即使 CSS 很早注入也不会改变原始布局
// 注入 CSS.因为模块默认关闭, 即使 CSS 很早注入, 也不会改变原始布局.
inject();
// GM_addStyle 后再暴露一次 API保证 lastReport / style 状态更新
// GM_addStyle 后再暴露一次 API, 保证 lastReport / style 状态更新.
exposeDebugApi();
// 观察 head防止 style 被站点或扩展移除
// 观察 head, 防止 style 被站点或扩展移除.
startHeadObserver();
// 观察 body用于动态内容出现后触发尚未 ready 模块的探测
// 观察 body, 用于动态内容出现后触发尚未 ready 模块的探测.
startPageObserver();
if (!document.head && document.addEventListener) {
// 极早 document-start 时 head 可能还不存在DOMContentLoaded 再补一次注入和探测
// 极早 document-start 时 head 可能还不存在; DOMContentLoaded 再补一次注入和探测.
document.addEventListener("DOMContentLoaded", () => {
inject();
exposeDebugApi();
// DOMContentLoaded 后再等 600ms让 React/图片容器有时间完成初始布局
// DOMContentLoaded 后再等 600ms, 让 React/图片容器有时间完成初始布局.
scheduleMeasure("domcontentloaded", 600);
startHeadObserver();
startPageObserver();
@@ -1022,11 +1049,11 @@
}
if (typeof setTimeout === "function") {
// 固定时间点探测用于覆盖缓存未命中React 首屏慢图片容器稍后才出现等情况
// 固定时间点探测用于覆盖: 缓存未命中, React 首屏慢, 图片容器稍后才出现等情况.
setTimeout(() => { scheduleMeasure("after-800ms"); reportDiagnostics("after-800ms"); startPageObserver(); }, 800);
// 第二次探测给慢一点的页面留余量
// 第二次探测给慢一点的页面留余量.
setTimeout(() => { scheduleMeasure("after-2000ms"); reportDiagnostics("after-2000ms"); startPageObserver(); }, 2000);
// 最后一次兜底探测成功的模块会被 moduleReady 锁住不会重复相乘
// 最后一次兜底探测; 成功的模块会被 moduleReady 锁住, 不会重复相乘.
setTimeout(() => { scheduleMeasure("after-5000ms"); reportDiagnostics("after-5000ms"); startPageObserver(); }, 5000);
}

View File

@@ -256,8 +256,8 @@ const cacheFakeDocument = {
addEventListener() {},
};
const cacheStore = new Map([
["tm-thumb-scale:size-cache:v9", JSON.stringify({
schema: 9,
["tm-thumb-scale:size-cache:v11", JSON.stringify({
schema: 11,
scale: 2,
cardHeightScale: 1.55,
modules: {
@@ -350,15 +350,17 @@ vm.runInNewContext(source, {
localStorage: {
getItem() {
return JSON.stringify({
schema: 9,
schema: 11,
scale: 2,
cardHeightScale: 1.55,
modules: {
home: {
homeCard: { width: 174, height: 120 },
homeThumb: { width: 174, height: 98 },
},
followingList: {
followingListCard: { width: 190, height: 120 },
followingListThumb: { width: 188, height: 106 },
},
},
});
@@ -385,7 +387,7 @@ vm.runInNewContext(source, {
assert.equal(followingAttrs.get("data-tm-thumb-scale-following-list"), "1", "followed-cams should use its own cached list module");
assert.equal(followingAttrs.get("data-tm-thumb-scale-home"), undefined, "followed-cams should not apply homepage cache");
assert.equal(followingVars.get("--tm-thumb-following-list-width"), "380px");
assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), "226px");
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-width"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), "212px");
assert.equal(followingVars.get("--tm-thumb-home-width"), undefined);