JavaFX WebView设置文本选区遇IndexSizeException的解决咨询
解决JavaFX WebView设置文本选区的IndexSizeException问题
我使用JavaFX的WebView组件渲染HTML内容,需要实现获取和设置文本选区的getSelectedIndices和setSelectedIndices方法。其中getSelectedIndices可以正常返回选区的全局文本偏移量,但setSelectedIndices方法会抛出IndexSizeException异常;修改JS脚本后虽不再频繁报错,但无法正确选中预期范围的内容。
原代码示例
package hu.krisz; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.web.WebView; import javafx.stage.Stage; public class WebViewExample extends Application { private String content = "<h1>Title</h1> <h2>SubTitle</h2> <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr</p>"; @Override public void start(Stage primaryStage) { WebView webView = new WebView(); webView.getEngine().loadContent(content); Button button = new Button("Select"); button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { int[] indices = getSelectedIndices(webView); System.out.println(indices[0] + ", " + indices[1]); // 测试手动设置偏移量 // indices[0] = 1; // indices[1] = 4; setSelectedIndices(webView, indices); } }); VBox vbox = new VBox(button); BorderPane root = new BorderPane(); root.setTop(vbox); root.setCenter(webView); Scene scene = new Scene(root, 800, 600); primaryStage.setTitle("WebView Példa"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } public int[] getSelectedIndices(WebView webView) { String script = "var selection = window.getSelection();" + "if (selection.rangeCount > 0) {" + " var range = selection.getRangeAt(0);" + " var preSelectionRange = range.cloneRange();" + " preSelectionRange.selectNodeContents(document.body);" + " preSelectionRange.setEnd(range.startContainer, range.startOffset);" + " var start = preSelectionRange.toString().length;" + " var end = start + range.toString().length;" + " start + ',' + end;" + "} else {" + " '0,0';" + "}"; String result = (String) webView.getEngine().executeScript(script); int[] indices = new int[2]; if (result != null && !result.isEmpty()) { String[] splitResult = result.split(","); indices[0] = Integer.parseInt(splitResult[0]); indices[1] = Integer.parseInt(splitResult[1]); } else { indices[0] = 0; indices[1] = 0; } return indices; } // 原错误的setSelectedIndices方法 public void setSelectedIndices(WebView webView, int[] indices) { String script = "function selectText(start, end) {" + " var range = document.createRange();" + " var selection = window.getSelection();" + " range.setStart(document.body.firstChild, start);" + " range.setEnd(document.body.firstChild, end);" + " selection.removeAllRanges();" + " selection.addRange(range);" + "}" + "selectText(" + indices[0] + ", " + indices[1] + ");"; webView.getEngine().executeScript(script); System.out.println("SELECTED FINSIHED."); } }
问题原因
- 节点类型错误:原脚本中使用
document.body.firstChild或document.body作为range.setStart的目标节点,这些都是元素节点,而非文本节点。range.setStart的第二个参数仅对文本节点表示字符偏移量,对元素节点则表示子节点的索引,传入文本长度偏移量必然超出范围,触发IndexSizeError。 - 全局偏移量不匹配:
getSelectedIndices计算的是整个body文本内容的全局字符偏移量,但设置选区时不能直接将该偏移量应用到单个节点上,需要遍历所有文本节点,累加长度找到对应偏移所在的具体文本节点。
解决方案
修改setSelectedIndices方法中的JS脚本,实现一个遍历文本节点、根据全局偏移量定位选区的函数:
public void setSelectedIndices(WebView webView, int[] indices) { String script = "function selectByGlobalOffset(startOffset, endOffset) {" + " var selection = window.getSelection();" + " selection.removeAllRanges();" + " var range = document.createRange();" + " var currentTextLength = 0;" + " var textNodes = [];" + " " + " // 递归收集所有非空文本节点" + " function collectTextNodes(node) {" + " if (node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0) {" + " textNodes.push(node);" + " } else {" + " for (var i = 0; i < node.childNodes.length; i++) {" + " collectTextNodes(node.childNodes[i]);" + " }" + " }" + " }" + " " + " collectTextNodes(document.body);" + " " + " var startNode = null;" + " var startPos = 0;" + " var endNode = null;" + " var endPos = 0;" + " " + " // 定位起始位置对应的文本节点和偏移" + " for (var i = 0; i < textNodes.length; i++) {" + " var nodeLength = textNodes[i].textContent.length;" + " if (currentTextLength + nodeLength > startOffset) {" + " startNode = textNodes[i];" + " startPos = startOffset - currentTextLength;" + " break;" + " }" + " currentTextLength += nodeLength;" + " }" + " " + " // 定位结束位置对应的文本节点和偏移" + " currentTextLength = 0;" + " for (var i = 0; i < textNodes.length; i++) {" + " var nodeLength = textNodes[i].textContent.length;" + " if (currentTextLength + nodeLength > endOffset) {" + " endNode = textNodes[i];" + " endPos = endOffset - currentTextLength;" + " break;" + " }" + " currentTextLength += nodeLength;" + " }" + " " + " // 应用选区范围" + " if (startNode && endNode) {" + " range.setStart(startNode, startPos);" + " range.setEnd(endNode, endPos);" + " selection.addRange(range);" + " }" + "}" + "selectByGlobalOffset(" + indices[0] + ", " + indices[1] + ");"; webView.getEngine().executeScript(script); System.out.println("SELECTED FINISHED."); }
说明
- 收集文本节点:通过递归遍历
document.body下的所有节点,筛选出非空的文本节点,避免元素节点干扰。 - 定位偏移位置:遍历文本节点,累加文本长度,找到全局偏移量对应的具体文本节点和节点内的字符偏移。
- 设置选区:使用
range.setStart和range.setEnd在正确的文本节点上设置偏移,最后添加到选区中。
这样修改后,setSelectedIndices方法可以正确对应getSelectedIndices返回的全局偏移量,不会再抛出异常,且能准确选中预期的文本范围。
内容的提问来源于stack exchange,提问作者Krisztofer Török
相关产品推荐
相关产品推荐

