CKEditor 4 表格缩放回归测试:Table Resize 在最大化 Divarea 编辑器与绝对定位容器中的兼容性验证

原创2026-10-15 22:59:411,258 阅读
文章标签:前端UI组件

CKEditor 4 表格缩放回归测试:Table Resize 在最大化 Divarea 编辑器与绝对定位容器中的兼容性验证

本文以 CKEditor 4 仓库中的 Bender 手动测试文档 maximizedinlinedivarea.md 为核心,解析其背后的两个历史缺陷(#909、#1959)与修复验证流程。读者将掌握该手动测试用例的执行方法、元标签语义,以及 tableresize、divarea、maximize 三个插件协同工作时"支柱(pillar)"定位机制与事件监听目标的源码级原理。

测试用例定位:一份面向 4.14.1 回归修复的 Bender 手动测试

该文档位于 tests/plugins/tableresize/manual/ 目录,属于 CKEditor 4 基于 Bender.js 的**手动测试(manual test)**体系。它不包含可断言的自动化断言,而是以"操作步骤 + 预期结果(Expected)/ 意外结果(Unexpected)"的形式,指引测试者在真实浏览器中人工验证某项功能。

@bender-ui: collapsed
@bender-tags: bug, 909, 1959, 4.14.1
@bender-ckeditor-plugins: toolbar, wysiwygarea, sourcearea, divarea, floatingspace, tableresize, maximize

1. Try to resize table inside editor.

### Expected

It is possible to resize the table or any of its columns (using CKEditor 4 resizer).

### Unexpected

Not possible to resize the table (resize cursor is not visible).

元标签(Meta Tags)逐项解读

  • @bender-ui: collapsed:指示 Bender 测试服务器在加载该用例时将 UI 折叠,仅展示测试相关区域,减少页面干扰。
  • @bender-tags: bug, 909, 1959, 4.14.1:声明这是缺陷回归测试,直接关联两个 GitHub issue 编号 909 与 1959,并标注修复版本 4.14.1。这二者在 CHANGES.md 的 "CKEditor 4.14.1" 版本条目中有明确记载:
    • #909:Table Resize 插件在编辑器位于绝对定位容器中时无法工作;
    • #1959:启用 Div Editing Area(divarea) 功能后,Table Resize 插件在**最大化(maximized)**编辑器中无法工作。
  • @bender-ckeditor-plugins:声明测试页面需要加载的插件集合。本用例同时加载 toolbar、wysiwygarea、sourcearea、divarea、floatingspace、tableresize、maximize,意在构造"内联编辑 + divarea + maximize"叠加的最复杂环境,验证修复在组合场景下依然有效。

测试环境构造:绝对定位容器内的内联编辑器

与 .md 文档配套的 maximizedinlinedivarea.html 精确复现了缺陷 #909 的触发条件:

<style>
	.container {
		position: absolute;
		width: 800px;
		height: 300px;
		z-index: 10000;
	}
</style>

<h2>Inline editor</h2>

<div class="container">
	<div id="inline" contenteditable="true">
		<p>Inline editor</p>
		<table border="1" cellpadding="1" cellspacing="1" style="width:600px">
			<!-- 含 thead/tbody、colspan 合并单元格、多样列宽的表格 -->
		</table>
	</div>
</div>

<script>
	if ( bender.tools.env.mobile ) {
		bender.ignore();
	}

	CKEDITOR.inline( 'inline' );
</script>

关键设计意图:

  1. position: absolute 容器:这正是 #909 的复现条件——表缩放光标依赖页面级坐标计算,绝对定位容器在旧版本中会导致坐标偏移,使缩放命中检测失效。
  2. CKEDITOR.inline() 创建内联编辑器:内联模式下编辑区域即页面 DOM 本身,tableresize 的事件监听目标与经典模式不同(下文详述),叠加绝对定位后问题更加隐蔽。
  3. 移动端跳过(bender.ignore()):触摸设备没有悬停光标,无法触发 mousemove 驱动的缩放交互,故在 bender.tools.env.mobile 为真时直接忽略本用例。

源码纵深:tableresize 的"支柱"定位机制

tableresize 插件的核心实现位于 plugins/tableresize/plugin.js。其交互模型分为三步:

1. 构建柱子(Pillars)——虚拟列边界

当鼠标在表格区域移动时,buildTableColumnPillars() 会遍历表格所有行(使用 CKEDITOR.tools.buildTableMap 构建行映射以正确处理 rowspan,见 #3961),在每个单元格边界处计算虚拟的"柱子"对象:

pillar = {
	table: table,
	index: pillarIndex,
	x: pillarLeft,
	y: pillarPosition.y,
	width: pillarWidth,
	height: pillarHeight,
	rtl: rtl
};

柱子位置基于 td.getDocumentPosition().x、offsetWidth 与边框宽度计算,并在 RTL 方向下做左右镜像处理(plugins/tableresize/plugin.js)。计算结果以 _cke_table_pillars 自定义数据缓存在表格元素上,避免每帧重复计算;同时注册 mouseout / mousedown 监听器在交互后清空缓存(clearPillarsCache)。

2. 命中检测——getPillarAtPosition

checkWithinDimensions() 用当前鼠标坐标与每个柱子的矩形区域做包含性判断,命中后由 columnResizer 的 attachTo() 将缩放条(resizer)定位到该列边界,并把 document.body 的光标切换为 col-resize(plugins/tableresize/plugin.js)。

3. 拖拽缩放——边界约束与撤销快照

resizeStart() 在按下鼠标瞬间,通过 buildTableMap 计算该列左右两侧所有单元格,并缓存最小可缩放边界 leftShiftBoundary / rightShiftBoundary,拖拽过程中用 Math.max / Math.min 将位移钳制在边界内;单元格最小宽度被限制为 1px(#11626)。缩放完成后异步写入各单元格宽度,并在所有单元格处理完毕后触发 editor.fire( 'saveSnapshot' ) 记录撤销快照(#13388),从而保证表格缩放可作为独立步骤被撤销(详见自动化测试 'test undo/redo table resize')。

缺陷根源:事件监听目标与坐标系的错位

tableresize 的插件初始化在 contentDom 事件中完成,其中最关键的一行决定了它的事件挂载点:

// In Classic editor it is better to use document
// instead of editable so event will work below body.
listenerTarget = editable.isInline() ? editable : editor.document;

(plugins/tableresize/plugin.js)

  • 经典编辑器(wysiwygarea):编辑内容位于 iframe 内,监听 editor.document 上的 mousemove;
  • 内联编辑器:编辑区域是页面文档的一部分,直接监听 editable 元素。

修复 #909/#1959 的关键逻辑还包括对表格归属的强校验:

// Make sure the table we found is inside the container
// (eg. we should not use tables the editor is embedded within)
if ( !editor.editable().contains( table ) ) {
	return;
}

(plugins/tableresize/plugin.js)

这条守卫防止编辑器嵌入在页面其他表格中时(如"编辑器放在表格单元格里")误把外层表格当作可缩放对象。自动化测试 'test preventing creating pillars on tables out of editor' 专门验证了该逻辑:位于编辑区之外的表不会生成柱子缓存。

同时,插件在 #4889 后新增了滚动监听——在 scroll 事件触发时通过 CKEDITOR.tools.debounce(..., 200) 延迟清理所有表格的柱子缓存,防止滚动后柱子坐标过期导致缩放光标错位(对应手动测试 scrollupdate.md)。

组合场景剖析:divarea 与 maximize 如何影响缩放

divarea:用 DIV 替换 iframe 的"wysiwyg"模式

plugins/divarea/plugin.js 在 afterInit 阶段通过 editor.addMode( 'wysiwyg', ... ) 覆盖 wysiwygarea 注册的模式,改用页面内 <div class="cke_wysiwyg_div cke_reset cke_enable_context_menu"> 作为编辑区域。由于不再有 iframe 隔离,"编辑器内文档"与"页面文档"混在同一坐标系中,任何对坐标(如 getDocumentPosition、getPageOffset)的误用都会在最大化、绝对定位等场景下被放大。

maximize:全屏时改变容器几何与层叠

plugins/maximize/plugin.js 的 maximize 命令在激活时会对编辑器容器及其所有祖先节点保存并重置样式,设置 z-index: editor.config.baseFloatZIndex - 5,并将容器定位到 (0,0) 并铺满视口(resizeHandler 调用 editor.resize)。这意味着最大化后编辑器的布局位置发生剧烈变化,若柱子的坐标缓存未同步更新,缩放命中就会失效——这正是 #1959 的典型表现。

注意:maximize 插件本身在内联模式下不可用(editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE 时直接返回),因此本测试文档(内联 + maximize 插件同时加载)更多是验证插件共存时的健壮性;真正完整复现"最大化后缩放"的用例是姊妹文档 maximizeddivarea.md,它在经典编辑器(removePlugins: 'divarea')与 Divarea 编辑器两个实例中分别执行"点击最大化按钮 → 缩放表格"的完整流程:

@bender-tags: bug, 909, 1959, 4.14.1
@bender-ckeditor-plugins: toolbar, wysiwygarea, sourcearea, divarea, tableresize, maximize

*Repeat below steps in all editors.*

1. Maximize editor by pressing maximize button.
1. Try to resize table inside editor.

### Expected
It is possible to resize the table or any of its columns (using CKEditor 4 resizer).

### Unexpected
Not possible to resize the table (resize cursor is not visible).

自动化测试佐证:从手动到可断言的覆盖

手动测试之外,tests/plugins/tableresize/tableresize.js 用 YUI + Sinon 提供了自动化回归保障。其核心思路是模拟事件序列:

  • createMoveEventMock 构造带 getPageOffset / getTarget 的 mousemove 事件 mock,init() 连续触发两次 mousemove——第一次生成柱子缓存,第二次创建 resizer;
  • resize() 依次触发 resizer 的 mousedown、两次 mousemove 和文档的 mouseup,模拟完整拖拽,再以 setTimeout 异步回调断言表格宽度确实被改变;
  • 用例覆盖经典编辑器、内联编辑器、编辑器外表格隔离、撤销/重做('test undo/redo table resize')、空表与仅含 thead/tfoot 的表、滚动后柱子重置('test pillars are resetted on scroll')等场景。

bender 配置位于 bender.js(测试分组 Plugins 覆盖 plugins/** 路径)与 bender-runner.config.json(服务器端口 9001/9002,Linux 上默认 Chrome/Firefox,macOS 上默认 Safari)。

测试执行与判定速览

项目 说明
测试文件 tests/plugins/tableresize/manual/maximizedinlinedivarea.md(配套 .html)
触发场景 绝对定位容器内的内联编辑器;联动检查 divarea + maximize 共存
操作步骤 在编辑器内直接尝试拖拽表格列边界
预期通过 出现 col-resize 光标,可缩放表格或任意列
失败判据 光标不可见、无法缩放(复现 #909 / #1959)
修复版本 CKEditor 4.14.1,详见 CHANGES.md
源码依据 plugins/tableresize/plugin.js、plugins/divarea/plugin.js、plugins/maximize/plugin.js

结语

从这份不足 20 行的手动测试文档可以窥见 CKEditor 4 回归测试的严谨:它用元标签将用例与 GitHub issue、修复版本一一绑定,用精心构造的 HTML 精确复现绝对定位与最大化叠加的极端环境,再以"预期/意外"二分法为测试者提供明确判定标准。而深入源码可以看到,tableresize 的柱子坐标系、事件监听目标选择(内联 vs 经典)与表格归属校验,正是这些场景能否正常工作的底层关键。理解这条从"缺陷 → 手动复现 → 源码修复 → 自动化断言"的链路,对维护富文本编辑器中的复杂交互组件具有直接的参考价值。

登录后查看全文
ckeditor4