You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:39:53