前端UI组件【免费下载链接】ckeditor4The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.项目地址https://gitcode.com/gh_mirrors/ck/ckeditor4点击查看免费下载导读在 CKEditor 4 中启用 Table Selection表格选择插件后用户可以用鼠标框选整行/整列单元格并按下Backspace、Delete键直接删除选中行。但如果编辑器处于只读read-only状态这种选中即删除的交互就会绕过只读保护造成表格内容被意外清空。本文以仓库中的手动测试用例 readonly.md 为线索完整还原该缺陷GitHub Issue #1489的复现步骤、预期行为并结合 plugin.js 与 editor.js 的源码剖析修复原理同时给出自动化测试用例的解读帮助你理解并验证只读模式下表格选择的安全行为。背景一个被 #1489 修复的只读模式漏洞Bug 来源与修复版本在 CKEditor 4.11.0 之前的版本中存在这样一个缺陷当 Table Selection 插件启用时编辑器处于只读模式下表格内容仍然可以被删除。该问题在 CHANGES.md 中被明确记录Fixed:Table contents can be removed in read-only mode when the [Table Selection] plugin is used.对应 Issue #1489于 4.11.0 版本修复同类问题在历史上并不少见例如#1516伪造选择fake selection允许在只读模式下用Backspace/Delete键删除内容#1570伪造选择允许在只读模式下用Ctrl/CmdX剪切内容#808 / #3260Widget 在只读模式下拖拽导致内容消失、拖拽手柄仍然可见#2816 / #2874增强图片image2在只读模式下出现调整大小手柄。这些条目共同说明CKEditor 4 的只读保护需要插件层面逐一加固而表格选择的键盘删除路径正是其中之一。手动测试用例的定位仓库中用于验证该修复的手动测试用例位于 tests/plugins/tableselection/manual/readonly.md配套的可运行页面为 tests/plugins/tableselection/manual/readonly.html。这两个文件共同构成了一个典型的 Bender 手动测试manual test用例供 QA 或开发者按步骤在真实浏览器中验证行为。手动测试用例逐条解读测试用例文档readonly.md全文语义bender-ui: collapsed bender-tags: 1489, 4.11.0, bug bender-ckeditor-plugins: wysiwygarea, toolbar, tableselection 1. Focus the editor. 1. Select the first row in a table. 1. Press backspace key. 1. Repeat 1-3 with delete key. 1. Repeat 1-3 with a, k keys (random key validation). ## Expected Selected row didnt change. ## Unexpected Selected row has been deleted.先解读头部三个 Bender 指令bender-ui: collapsed手动测试页面在 Bender 测试面板中以折叠方式展示便于聚焦编辑器本身bender-tags: 1489, 4.11.0, bug标记该用例关联的 Issue 编号1489、修复版本4.11.0与类型缺陷回归验证bender-ckeditor-plugins: wysiwygarea, toolbar, tableselection声明测试所需的最小插件集——经典编辑器编辑区wysiwygarea、工具栏toolbar以及被测对象表格选择tableselection。五步操作流程的核心意图Focus the editor聚焦编辑器保证键盘事件进入编辑器实例同时让表格选择处于激活状态Select the first row in a table选中表格第一行通过 Table Selection 的伪造选择机制选中整行单元格Pressbackspacekey按下退格键验证退格键不会删除选中行Repeat 1-3 withdeletekey用删除键重复 1-3验证Delete键同样无效Repeat 1-3 witha,kkeys用随机字符键重复 1-3这是随机键验证random key validation——因为表格伪造选择状态下字符输入同样会先清空单元格再插入因此需要确认任意普通按键在只读模式下都不会触发清空操作。预期结果Expected被选中的行不发生任何变化编辑器内容保持完整。意外结果Unexpected选中的行被整体删除——这正是 #1489 描述的缺陷行为。测试页面readonly.html的搭建方式配套页面 readonly.html 包含一个 3 行 × 4 列的标准表格Cell 1.1.1到Cell 1.3.4并在脚本中通过配置项直接初始化只读编辑器div ideditor1 table border1 tr tdCell 1.1.1/td tdCell 1.1.2/td tdCell 1.1.3/td tdCell 1.1.4/td /tr !-- 共 3 行每行 4 个单元格 -- /table /div script if ( bender.tools.env.mobile ) { bender.ignore(); } bender.tools.ignoreUnsupportedEnvironment( tableselection ); CKEDITOR.replace( editor1, { readOnly : true } ); /script这里有两个值得注意的细节readOnly : true是核心开关它通过配置项让编辑器一启动就进入只读模式区别于运行时再调用setReadOnly()环境守卫移动端环境直接bender.ignore()跳过本用例同时bender.tools.ignoreUnsupportedEnvironment( tableselection )会在浏览器不支持该插件如 IE 11 以下时自动忽略——这与插件自身isSupportedEnvironment()的限制保持一致见下文源码分析。源码级修复原理只读模式下拦截一切键盘编辑路径修复点一keydown 监听器中的editor.readOnly守卫修复的核心在 plugins/tableselection/plugin.js。Table Selection 的键盘集成通过keyboardIntegration( editor )注入其中getTableOnKeyDownListener返回的处理器管理着方向键、退格、删除与回车键在表格伪造选择下的行为var keystrokes { 37: 1, // Left Arrow 38: 1, // Up Arrow 39: 1, // Right Arrow, 40: 1, // Down Arrow 8: 1, // Backspace 46: 1, // Delete 13: 1 // Enter }, ... return function( evt ) { // Use getKey directly in order to ignore modifiers. var key evt.data.getKey(), keystroke evt.data.getKeystroke(), ... // Handle only left/right/del/bspace keys. // Disable editing cells in readonly mode (#1489). if ( !keystrokes[ key ] || editor.readOnly ) { return; } ... };关键行是if ( !keystrokes[ key ] || editor.readOnly ) { return; }!keystrokes[ key ]非表格选择管理的按键例如a、k这类普通字符键直接不处理editor.readOnly只要编辑器处于只读状态即使按键命中管理列表8 退格、46 删除、13 回车、37-40 方向键也立即 return不执行任何后续的单元格清空或删除逻辑。这就是修复 #1489 的第一道闸门它把删除整行/清空单元格这条路径在只读模式下彻底短路。修复点二keypress 监听器中的editor.readOnly守卫伪造选择状态下还有一个独立的字符输入路径——tableKeyPressListenerplugin.js。它的职责是处理用户在选中单元格后直接输入字符的情况先清空被选单元格再把字符放入首个单元格这也是手动测试中随机键验证步骤 5要覆盖的场景function tableKeyPressListener( evt ) { var selection editor.getSelection(), // Enter key also produces character, but Firefox doesnt think so (gh#415). isCharKey evt.data.$.charCode || ( evt.data.getKey() 13 ), ... // Disable editing cells in readonly mode (#1489). if ( editor.readOnly ) { return; } ... }这里在判断isCharKey之前就率先检查editor.readOnly——只读模式下连字符键的清空后插入行为也被一并拦截。注意该判断位于是否产生字符判断之前意味着只要编辑器只读无论按什么键包括 Gecko 内核浏览器中所有按键都会触发 keypress 的情况都不会进入清空流程。两个监听器的挂载与优先级这两个监听器在keyboardIntegration( editor )末尾统一挂载plugin.jsvar editable editor.editable(); editable.attachListener( editable, keydown, getTableOnKeyDownListener( editor ), null, null, -1 ); editable.attachListener( editable, keypress, tableKeyPressListener, null, null, -1 ); editable.attachListener( editable, keyup, getTableKeyUpListener( editor ), null, null, -1 );监听优先级为-1低优先级配合attachListener与 editable 生命周期绑定确保编辑器销毁或重新加载内容时监听器随之清理。此外getTableKeyUpListener专门处理Tab键keyCode 9在表格选择中的导航并负责恢复伪造选择restoreFakeSelection它本身不涉及内容修改因此无需只读守卫。被拦截的下游逻辑清空与删行一旦没有editor.readOnly守卫被选中的表格元素会走上两条删除路径可对照理解为什么会有整行被删除的意外结果clearCellInRange( range )plugin.js对td/th调用node.setText( )清空文本对其他元素调用range.deleteContents()删除内容随后为未删除的单元格补充 bogusbr防止行塌陷deleteEmptyTablePart( node, ranges )plugin.js当选中范围完整覆盖tr甚至table时会放大范围边界节点并执行mergedRanges[ 0 ].deleteContents()从而直接删除整行或整张表格——这正是手动测试Unexpected: Selected row has been deleted所描述的现象来源。只读守卫正是为了让这两条路径在只读模式下永远不被触发。只读机制的底层支撑editor.readOnly 从何而来配置项config.readOnlyeditor.readOnly属性由核心在配置加载阶段初始化。在 core/editor.js 的onConfigLoaded中editor.readOnly isEditorReadOnly(); function isEditorReadOnly() { if ( config.readOnly ) { return true; } if ( editor.elementMode CKEDITOR.ELEMENT_MODE_INLINE ) { if ( editor.element.is( textarea ) ) { return editor.element.hasAttribute( disabled ) || editor.element.hasAttribute( readonly ); } else { return editor.element.isReadOnly(); } } else if ( editor.elementMode CKEDITOR.ELEMENT_MODE_REPLACE ) { return editor.element.hasAttribute( disabled ) || editor.element.hasAttribute( readonly ); } return false; }从源码结构可以看出editor.readOnly的判定优先级为config.readOnly配置为true——即手动测试中CKEDITOR.replace( editor1, { readOnly : true } )的用法这是最直接的开关替换模式replace被替换的textarea带disabled或readonly属性内联模式inlinetextarea 同样看disabled/readonly属性非 textarea 元素则看element.isReadOnly()即元素或其祖先是否带contenteditablefalse。config.readOnly的完整说明记录在 core/editor.js默认值为false。运行时切换setReadOnly()除了启动时通过配置进入只读也可以在运行时调用editor.setReadOnly( isReadOnly )core/editor.jssetReadOnly: function( isReadOnly ) { isReadOnly ( isReadOnly null ) || isReadOnly; if ( this.readOnly ! isReadOnly ) { this.readOnly isReadOnly; // Block or release BACKSPACE key according to current read-only // state to prevent browsers history navigation. this.keystrokeHandler.blockedKeystrokes[ 8 ] isReadOnly; this.editable().setReadOnly( isReadOnly ); // Fire the readOnly event so the editor features can update // their state accordingly. this.fire( readOnly ); } }该方法同步完成四件事更新readOnly属性、按状态拦截/放行全局BackspacekeyCode 8防止浏览器历史回退、设置编辑区editable只读、触发readOnly事件供各插件刷新自身状态。Table Selection 插件的两个键盘监听器读取的正是这个editor.readOnly属性因此无论是启动配置还是运行时切换都能被正确感知。插件对环境与依赖的约束从 plugin.js 的插件定义可以看到两条重要约束CKEDITOR.plugins.add( tableselection, { requires: clipboard,tabletools, isSupportedEnvironment: function() { return !( CKEDITOR.env.ie CKEDITOR.env.version 11 ); }, ... init: function( editor ) { // Disable unsupported browsers. if ( !this.isSupportedEnvironment() ) { return; } ... } } );插件依赖clipboard与tabletools所以手动测试只声明三个插件也能正常工作wysiwygarea、toolbar 是编辑器外壳tableselection 会拉取其 requires 依赖IE 11 以下不支持该插件这也是readonly.html中调用ignoreUnsupportedEnvironment( tableselection )的原因。另外伪造选择的视觉实现通过 CSS 类cke_table-faked-selection呈现plugin.js并在init中通过editor.addContentsCss( this.path styles/tableselection.css )注入内容样式鼠标选择路径则由fakeSelectionMouseHandler监听 document 上的mousedown/mousemove/mouseup事件驱动plugin.js与键盘路径相互独立。自动化测试与手动用例对应的回归断言除了手动测试仓库还提供了对应的自动化回归用例test random keys are not removing readonly selectiontests/plugins/tableselection/tableselection.js// (#1489) test random keys are not removing readonly selection: function( editor ) { var selection editor.getSelection(), editable editor.editable(), table CKEDITOR.document.getById( simpleTable ).getHtml(); editor.setReadOnly( true ); bender.tools.setHtmlWithSelection( editor, table ); var row editor.editable().findOne( tr ); selection.selectElement( row ); editable.fire( keydown, new CKEDITOR.dom.event( { keyCode: 8 } ) ); // backspace editable.fire( keydown, new CKEDITOR.dom.event( { keyCode: 46 } ) ); // delete editable.fire( keypress, new CKEDITOR.dom.event( { keyCode: 65, charCode: 65 } ) ); // a editable.fire( keypress, new CKEDITOR.dom.event( { keyCode: 93, charCode: 93 } ) ); // t editor.setReadOnly( false ); assert.areSame( bender.tools.compatHtml( table ), editor.getData(), Editor data ); }这个用例与手动测试一一对应手动测试步骤自动化测试对应动作聚焦编辑器setHtmlWithSelection建立内容与选区选中第一行selection.selectElement( row )选中整行tr按backspace触发keydownkeyCode 8按delete触发keydownkeyCode 46按a、k随机键触发keypresskeyCode/charCode 65a与 93t断言选中行未改变assert.areSame( compatHtml( table ), editor.getData() )测试的关键手法是先editor.setReadOnly( true )再模拟按键事件最后恢复可编辑状态并断言getData()与初始 HTML 完全一致——任何被clearCellInRange/deleteEmptyTablePart触发的清空或删行都会导致断言失败。该用例位于测试套件文件头部声明的两个编辑器classic与inline见 tableselection.js上各运行一遍覆盖两种编辑模式。如何在本地运行与验证运行手动测试手动测试用例依赖 Bender 测试框架仓库根目录的 bender.js 与 bender.ci.js 提供了测试配置。典型流程安装依赖并启动 Bender 服务npm install后运行 bender 相关脚本在浏览器中打开 Bender 测试面板定位到tests/plugins/tableselection/manual/readonly.html按 readonly.md 的五步操作逐一执行重点观察步骤 3-5 中选中行是否保持不变对照Expected / Unexpected判定结果。若想亲眼看到修复前的缺陷行为可以临时移除 plugin.js 中的editor.readOnly判断后重新构建——但请注意仓库为只读资源此操作仅用于本地实验验证不应修改仓库文件。更稳妥的方式是直接运行上文的自动化测试用例来验证当前代码的行为。自动化测试的运行范围自动化用例位于 tests/plugins/tableselection/tableselection.js文件头部声明了bender-ckeditor-plugins: tableselection, clipboard、bender-ckeditor-remove-plugins: undo并引入了_helpers/tableselection.js中的tableSelectionHelpers与mockMouseSelection辅助函数。运行该套件即可将 readonly 回归用例与其余表格选择测试合并单元格、嵌套表格、粘贴等一并验证。总结与延伸阅读围绕 #1489CKEditor 4 在 4.11.0 中通过在键盘删除路径与字符输入路径两处增加editor.readOnly守卫修复了只读模式下表格内容可被删除的缺陷。这套手动测试 自动化回归 源码守卫的组合体现了该项目的质量保障模式手动测试readonly.md / readonly.html用于真实浏览器体验验证自动化测试tableselection.js用模拟事件保证持续回归源码守卫plugin.js 与 plugin.js从机制上杜绝绕过。只读保护在整个项目中是一个系统性工程除表格选择外CHANGES.md 中还记录了伪造选择剪切#1570、退格删除#1516、Widget 拖拽#808、增强图片缩放#2816 / #2874等一系列同类修复。理解editor.readOnly属性core/editor.js与config.readOnly配置项core/editor.js的联动机制是排查任何只读模式下内容仍被修改问题的起点——而 Table Selection 插件的两处守卫正是这一机制在表格场景下的标准实践。赞分享前端UI组件【免费下载链接】ckeditor4The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.项目地址https://gitcode.com/gh_mirrors/ck/ckeditor4点击查看免费下载相关推荐CKEditor 4 Find 插件只读模式查错trac12848 缺陷修复与 Bender 手动测试全解析CKEditor 4 Find 插件只读模式查错trac12848 缺陷修复与 Bender 手动测试全解析 导读 本文以 CKEditor 4 仓库中的一条前端UI组件CKEditor 4 Table Selection 插件按 Enter 键行为深度解析从手动测试用例到源码实现CKEditor 4 Table Selection 插件按 Enter 键行为深度解析从手动测试用例到源码实现 本文以 tests/plugins/tabl前端UI组件CKEditor 4 Table Selection 插件拖拽选区边界trac16755 手动测试用例与源码机制解读CKEditor 4 Table Selection 插件拖拽选区边界trac16755 手动测试用例与源码机制解读 导读 本文围绕 CKEditor 4 官前端UI组件上一篇如何用uPlot打造超高效实时图表从数据绑定到多图联动的完整指南下一篇缓存优化Quasar分布式设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考