Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Discontigious Selection is not supported error in google chrome and chromium

I am working on a bookmark app where i have to store the user's selected keywords or words or content. I am using the createRange() and addRange() javascript methods to create the range and then find out the selected elements/contents by the user. The code i written for this is as follow.

<head>
<script type="text/javascript">
    var storedSelections = [];

    function StoreSelection () {
        if (window.getSelection) {
            var currSelection = window.getSelection ();
            for (var i = 0; i < currSelection.rangeCount; i++) {
                storedSelections.push (currSelection.getRangeAt (i));
            }
            currSelection.removeAllRanges ();
        } else {
            alert ("Your browser does not support this example!");
        }
    }

    function ClearStoredSelections () {
        storedSelections.splice (0, storedSelections.length);
    }

    function ShowStoredSelections () {
        if (window.getSelection) {
            var currSelection = window.getSelection ();
            currSelection.removeAllRanges ();
            for (var i = 0; i < storedSelections.length; i++) {
                currSelection.addRange (storedSelections[i]);
            }
        } else {
            alert ("Your browser does not support this example!");
        }
    }
</script>
</head>
<body>
    Select some content on this page and use the buttons below.<br />         <br />
    <button onclick="StoreSelection ();">Store the selection</button>
    <button onclick="ClearStoredSelections ();">Clear stored selections
</button>
    <button onclick="ShowStoredSelections ();">Show stored selections</button>

</body>

This code is working perfectly on Firefox. I am able to select multiple things one by one and able to show the selected content again but on chrome and chromium i am getting Discontiguous selection is not supported. error when i store more than one elements in range array and click on show stored selections button.

Help will be appreciated. And please suggest me if there is some other alternatives do accomplish this bookmarking task.

like image 579
Sajid Ahmad Avatar asked Jan 29 '15 12:01

Sajid Ahmad


3 Answers

Write

window.getSelection().removeAllRanges();

immediately before creating range.

https://bugs.chromium.org/p/chromium/issues/detail?id=399791

like image 144
Shashikant Pandit Avatar answered Nov 06 '22 23:11

Shashikant Pandit


Here's the only possible way of doing this that I was able to come up with:

Wrap the selection in <span style="background: Highlight;">...</span>.

But note:

  • Obviously, you have to remove those spans again as soon as anything else is selected, but that shouldn't be too difficult. However, you should use window.onmousedown for that rather than window.onclick, because onclick is fired after any button is pressed, so when pressing your "Show stored selections" button, a new selection will be created, thus destroying the one that was supposed to be captured.
  • Removing or replacing any elements in which a stored selection starts or ends will invalidate that selection, so when clicking "Show stored selections", nothing will show up.
  • If the selection spans over multiple elements, it needs to split up into one selection for each element, otherwise inserting the span will either fail or cut other elements (like buttons) in half.

The following code (fiddle) is the best I was able to do:

var storedSelections = [];
var simulatedSelections = [];

window.onmousedown = clearSimulatedSelections;

function storeSelection()
{
    if(window.getSelection)
    {
        var currSelection = window.getSelection();
        for(var i = 0; i < currSelection.rangeCount; i++)
        {
            storeRecursive(currSelection.getRangeAt(i));
        }
        currSelection.removeAllRanges();
    }
    else
    {
        alert("Your browser does not support this example!");
    }
}

function storeRecursive(selection, node, started)
{
    node = node || document.body;
    started = started || false;
    var nodes = node.childNodes;
    for(var i = 0; i < nodes.length; i++)
    {
        if(nodes[i].nodeType == 3)
        {
            var first = nodes[i] == selection.startContainer;
            var last = nodes[i] == selection.endContainer;
            if(first)
            {
                started = true;
            }
            if(started)
            {
                var sel = selection.cloneRange();
                if(!first)
                {
                    sel.setStartBefore(nodes[i]);
                }
                if(!last)
                {
                    sel.setEndAfter(nodes[i]);
                }
                storedSelections.push(sel);
                if(last)
                {
                    return false;
                }
            }
        }
        else
        {
            started = storeRecursive(selection, nodes[i], started);
        }
    }
    return started;
}

function clearStoredSelections()
{
    storedSelections = [];
}

function showStoredSelections()
{
    if(window.getSelection)
    {
        var currSelection = window.getSelection();
        currSelection.removeAllRanges();
        for(var i = 0; i < storedSelections.length; i++)
        {
            var node = document.createElement("span");
            node.className = "highlight";
            storedSelections[i].surroundContents(node);
            simulatedSelections.push(node);
        }
    }
    else
    {
        alert("Your browser does not support this example!");
    }
}

function clearSimulatedSelections()
{
    for(var i = 0; i < simulatedSelections.length; i++)
    {
        var sec = simulatedSelections[i];
        var pn = sec.parentNode;
        while(sec.firstChild)
        {
            pn.insertBefore(sec.firstChild, sec);
        }
        pn.removeChild(sec);
    }
    simulatedSelections = [];
}
.highlight
{
    background: Highlight;
}
Select some content on this page and use the buttons below.<br><br>
<button onclick="storeSelection();">Store the selection</button>
<button onclick="clearStoredSelections();">Clear stored selections</button>
<button onclick="showStoredSelections();">Show stored selections</button>

It works in Firefox, Safari and Chrome, but has the following shortcomings:

  • Selections over multiple lines don't select the blank area between the end of the line and the border of the parent element, like actual selections do.
  • Sometimes when starting a selection at a point before the start of a stored selection, displaying them will merge the ranges, so the text in between is selected too. Sorting the array of stored selections doesn't seem to help.
  • In Safari, the tab crashed multiple times with a segmentation fault when selecting multiple lines and ending/starting a selection in the middle of a button's text.

However, I doubt that anything better is possible in browsers other than Firefox, but even Firefox has a ticket to drop discontiguous selections.

like image 21
Siguza Avatar answered Nov 06 '22 23:11

Siguza


FYI I was getting a similar error when rolling my own "copy to clipboard" feature. I'm not going to address OP's provided code, but I'll tell you how I fixed it in my own code.

Reproduce:

  1. Copy some other text on the page to the clipboard, e.g. "foo".
  2. Paste the text somewhere. It outputs "foo".
  3. Click your "copy to clipboard" button, which copies e.g. "bar".
  4. Paste the text somewhere.

Expected:

"bar" is outputted.

Actual:

"Discontiguous selection is not supported"

Fix:

Call window.getSelection().removeAllRanges() at the start of your "copy to clipboard" event handler. "Discontiguous" means "not connected". So my guess is that the browser copies the first range (the node containing "foo"), and then gets angry when you try to select another range that is not next to the first node.

like image 36
Kayce Basques Avatar answered Nov 07 '22 00:11

Kayce Basques