
事情从用户群里的一张截图开始:扩展弹窗的内容还老老实实挤在左边,右侧却凭空多出一大片空白,整个面板就像被什么看不见的“隐形力量”强行给撑开了。
最初我们以为这是个普通的 CSS Bug —— 是不是哪个组件改了宽度?是不是图片没约束?
可越查越不对劲:同一个版本每次打开宽度还不一样,删掉图片就好了,连一个没有任何业务代码、只有几行 HTML 的空页面,也能稳定被撑到 800px。
但接下来的排查却让人大跌眼镜,现象极其诡异且矛盾:
同一个版本,每次打开 Popup,异常宽度居然是随机的;
把图片资源全删掉,Popup 似乎又恢复了正常;

隔壁 AdBlock和沉浸式翻译的 Popup 也都出现了类似变宽的现象;

这些截图不能证明几个案例具有同一个根因,却共同指向了浏览器宿主的autosize:Popup 的窗口尺寸并不只由最终可见的业务组件决定。
于是我们写了一个没有业务代码、没有图片、没有异步 DOM 的纯 HTML 页面,只设置了316px 宽、650px 高。
html,body { margin: 0;}
.repro { box-sizing: border-box; width: 316px; height: 650px;}页面没有业务 CSS、脚本、图片或异步更新,唯一的特点是高度超过了 Chrome Extension Popup 的 600px 上限。结果 Popup 稳定扩展到了 800px 宽。

这意味着,我们面对的压根就不是一个简单的样式 Bug,而是两个最终表现极其相似、底层触发机制却完全不同的“硬核陷阱”。
到这里,两个问题终于可以拆开讨论了。
在搞清原因前,先补充一个关键背景:Chrome Extension Popup 并不是一个固定大小的窗口。
官方规定它的尺寸范围是 25 × 25 到 800 × 600 像素,并且会根据内容“自动适应”。
简单来说,你在网页里的每一次变动,都会触发一套“接力棒”机制:DOM/CSS/图片变动 → 渲染引擎重新计算排版 → 算出新的尺寸 → 一路向上通知 Chrome 原生窗口调整Bubble大小。
扩展页面中的每一次布局变化,都可能沿着下面的链路改变浏览器原生 Popup 窗口:
DOM / CSS / 图片状态变化 ↓Blink 重新执行 style 和 layout ↓FrameViewAutoSizeInfo 计算新的内容尺寸 ↓ResizeDueToAutoResize(new_size) ↓WebView::SetPreferredSize(new_size) ↓Chrome Views 调整 Popup Bubble相关入口包括:
FrameViewAutoSizeInfo::AutoSizeIfNeededExtensionViewViews::ResizeDueToAutoResizeWebView::ResizeDueToAutoResize理解这条链路后,两个异常就不再神秘了。
这个现象可以脱离业务代码稳定复现。最小实验让业务容器始终保持 316px ,在页面加载期间临时插入一个 720px 宽、 1px 高的元素,等待 500ms 后再把它缩回:
const transient = document.createElement("div");transient.style.cssText = "width: 720px; height: 1px;";document.body.prepend(transient);
await new Promise((resolve) => setTimeout(resolve, 500));
transient.style.width = "1px";打开 Popup 后,即使临时元素已经缩回、可见业务内容仍然只有 316px ,宿主窗口依然可能保持在扩宽后的尺寸。这里使用普通 div 是为了排除图片缓存、网络和解码时序;换成带原始尺寸的图片、未约束 SVG 或晚到的异步内容,也会经过相同的 autosize 路径。
结果哪怕元素已经缩回去了,可见内容只有316px,Chrome 窗口却依然死死卡在扩宽后的尺寸!
这个对照实验说明,瞬时横向越界本身就足以触发问题。
假设某个组件先把远程图片插入 DOM,负责约束图片的 CSS 则在组件提交后的 effect 中注入:
组件 DOM 提交 ↓<img> 以 naturalWidth 参与普通流布局 ↓Blink 计算出更大的 preferred width ↓Chrome 扩大 Popup ↓effect 执行,图片才获得 width / position / overflow 约束从最终 DOM 看,图片可能已经变成 width: 100% 或 position: absolute ,所以事后检查经常找不到越界元素。但 Chromium 已经在更早的一帧看到过更大的内容宽度。
字体加载、异步列表、未断行文本、SVG、Canvas 以及先渲染后隐藏的组件,都可能制造类似的瞬时状态。图片只是最常见、也最容易受缓存影响的一种。
查看 Chromium 源码发现,引擎在页面加载期有一套防抖逻辑: “允许变大,暂不允许变小”。
Blink 当前的 FrameViewAutoSizeInfo 中仍然存在明确的加载期防抖逻辑。简化后相当于:
if (!LoadEventFinished() && new_size < current_size) { change_size = false;}也就是说,在页面加载尚未结束时:
与此同时, ExtensionViewViews 在 Popup 尚未显示时,会把 renderer 上报的尺寸保存到 pending_preferred_size_ ;页面加载完成并变为可见时,再把这个尺寸应用到 WebView。
因此,一个短暂存在的超宽状态有机会成为 Popup 第一次显示时采用的尺寸。
需要注意的是,Chromium 并没有规定“第一次宽度会永久锁死”。Popup 显示后仍会接收后续的 preferred size。异常宽度最终没有缩回,通常还意味着:
4. 为什么每次打开得到的宽度可能不同
图片原始尺寸通常是固定的,但浏览器采样到的页面状态并不固定。每次打开 Popup 时,下列事件的先后顺序都可能变化:
因此,异常宽度不是简单的“图片宽度是多少,Popup 就是多少”,而是浏览器在加载过程里看见过哪些中间布局,以及哪一次测量成为了宿主窗口的有效输入。
第二种机制甚至不需要任何动态资源,纯 HTML 就能触发。因为在2026年6月Chromium 对尺寸计算算法做了一次调整。
其核心变化是:
在 AutoSizeUsesScrollWidthForOverflow 开启时,autosize 的宽度来源从页面的 intrinsic minimum width (内在最小宽度) 转向根文档布局盒的 ScrollWidth() :
int width = AutoSizeUsesScrollWidthForOverflowEnabled() ? document_layout_box->ScrollWidth().ToInt() : layout_view->ComputeMinimumWidth().ToInt();简单来说,就是官方换了一把“测量尺子”:以前测的是“内容最小需要多宽”,现在改成了测量“滚动盒子的总宽度(ScrollWidth)”。
这个功能当前被标记为 stable 并默认开启:
runtime_enabled_features.json5当一个 316px 宽的 Popup 高度超过 600px 时,可能形成如下反馈:
内容高度超过 600px ↓Popup 高度被限制,并进入根文档滚动布局 ↓根 html/body 作为普通块填充当前 viewport ↓autosize 读取 document ScrollWidth ↓当前 viewport 宽度被再次报告为 preferred width ↓Popup 最终稳定在最大宽度 800px这解释了为什么最小复现页面虽然明确写着 width: 316px ,Chrome 仍然把宿主 Popup 扩展到了 800 × 600 :被测量的不再只是那个 316px 的内容块,还包括已经参与布局的根文档滚动宽度。
Chromium 随后又提交了 Reinstate some of the min-content logic for auto resizing,说明这次从 minimum width 切换到 scroll width 后,确实还需要补回一部分 min-content 约束。
我们已经把纯 HTML 最小复现、feature flag A/B 结果和截图提交给 Chromium:[Extensions] Action popup expands from 316px to 800px when content height exceeds 600px。
该问题提交时被归类到 Chromium > Platform > Extensions ,初始状态为 New ,优先级为 P2 ;后续进展以 Issue Tracker 为准。
为了验证以上猜想,我们启动 Chrome 并强制关闭了这个新的算法开关,在不改动一字代码的情况下,那个 316 × 650 的纯 HTML 页面立刻恢复了正常!目前我们已将该 Bug 复现案提交至 Chromium 官方社区(Issue P2 级别)。
下面的实验使用 Chrome 151.0.7922.72 正式版(arm64):

为了确认最小复现不是普通 CSS 问题,我们使用隔离 Profile 启动 Chrome,并关闭该特性:
open -na "Google Chrome" --args \ --user-data-dir=/tmp/chrome-popup-autosize-off \ --disable-features=AutoSizeUsesScrollWidthForOverflow同一个 316 × 650 页面恢复了正常宽度。

这个实验只改变 Chromium feature flag,没有改变页面 HTML 和 CSS,因此可以把第二种异常明确归因于新的 autosize 路径。

只看最终 Elements 面板通常不够。更有效的方法是给 Popup 加入早期探针,按时间记录:
performance.now()window.innerWidth / outerWidthhtml.clientWidth / scrollWidthbody.clientWidth / scrollWidthPopup 根节点 clientWidth / scrollWidthMutationObserver 变更目标图片 load/error 与 naturalWidthstyle/link 插入时刻window resize 事件document.readyState推荐按下面的顺序缩小问题:
<head> ,排除动态 CSS 时序;599px 、 600px 和 601px ;--disable-features=AutoSizeUsesScrollWidthForOverflow 做因果验证。即使浏览器未来修正高度越界行为,Popup 仍然属于由外部宿主驱动的自动尺寸页面。比较稳妥的防线包括:
不要把关键的 width 、 max-width 、 overflow 和图片布局规则只放在组件 effect 中动态注入。它们更适合进入 Popup 静态 CSS 或首屏同步 <style> 。
如果同一套 Popup 还运行在移动端扩展中,不能直接给共享的 html 、 body 写死 316px 。更合适的做法是先建立所有平台共用的横向安全约束,再通过构建目标或明确的布局标记,只为桌面窄面板设置固定宽度:
html,body,#mount { box-sizing: border-box; min-width: 0; margin: 0; overflow-x: hidden;}
/* data-popup-layout 仅作示例,也可以由不同构建入口加载不同样式。 */:root[data-popup-layout="desktop"],:root[data-popup-layout="desktop"] body,:root[data-popup-layout="desktop"] #mount { width: 316px; max-width: 316px;}
:root[data-popup-layout="mobile"] body,:root[data-popup-layout="mobile"] #mount { width: 100%; max-width: 100vw;}重点不是所有平台都固定为 316px ,而是桌面 Popup 的根文档不能在首帧产生意外的横向 scrollWidth ;移动端仍应按宿主 viewport 响应式布局。不要只依赖宽度 media query 判断宿主类型,因为桌面 Popup 的异常 viewport 本身就可能达到 800px。
对于桌面 Chrome Popup,可以让业务容器承担纵向滚动,并把根文档控制在 Popup 上限内;移动端应使用自己的 viewport 高度约束,不应照搬这里的 600px :
html,body { overflow: hidden;}
.popup-root { box-sizing: border-box; width: 100%; max-width: 100%; max-height: 600px; overflow-y: auto; overflow-x: hidden;}img,svg,canvas { display: block; max-width: 100%;}对需要绝对定位的装饰图片,最好在元素进入 DOM 前就保证相应样式已经存在。
至少覆盖:
599 / 600 / 601px ;回头看,这次最容易踩的坑是看到 Banner、图片、某个业务版本“恰好”和问题一起出现,就急着宣布“抓到凶手了”。
真正让案情反转的,其实只是两个"做减法"的动作:把最终 DOM 和加载途中的瞬时 DOM 分开看;把扩展的版本变化和浏览器的版本变化分开验。
最后我们才确认,同一个“弹窗右边一大片空白”的表象,背后站着两套不同机制:一个来自资源、CSS 和 DOM 的加载时序,一个来自 Chromium 对 autosize WebView autosize 宽度计算方式的改动。
这次事故也带来了一条产品层面的提醒:Popup 适合承载少量、高频、立即可操作的功能,不适合不断叠加入口和信息。内容越多、结构越复杂,越容易接近浏览器的尺寸上限,也会放大资源加载和布局时序带来的风险;次要功能更适合进入二级页面或 Side Panel。幸运的是,我们已经具备远程切换新版面板的能力,因此可以及时用更简洁的新版面板替换旧版,避开这次问题的触发条件。不过,这属于产品侧的快速止血,底层的 autosize 边界仍然需要通过布局约束和回归测试持续防守。
说到底,对跑在浏览器宿主里的前端页面来说,CSS 决定的从来不只是“内容长什么样”——某些时刻,它还悄悄决定了浏览器愿意给你多大的窗口。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。