javascriptselectionchromiummozillagetselection

Selection: incorrect isCollapsed value for deselecting by clicking on selected text


Problem

I need to detect selected text based on mouse events. I also need to detect when current selection disappears. My next code works fine for situation when you selecting something and deselecting this by clicking outside of selection. But this code doesn't works when you want to deselect by clicking on selected text.

Please test next code to see what i mean.

Environment

How to reproduce

  1. Open some page with some text (text should be represented as text node).
  2. Paste this code into console:
let mousePressed = false;

document.addEventListener('mousedown', () => {
    mousePressed = true;
});

document.addEventListener('mouseup', () => {
    if (mousePressed) {
        const selection = window.getSelection();
        const isCollapsed = selection.isCollapsed;

        console.log(`Collapsed: ${isCollapsed}`);

        mousePressed = false;
    }
});
  1. Perform double left click on some word. You will see this collapse sequence: false, true.
  2. Perform single left click outside of selected word (for example, by clicking on page background). You will see this collapse sequence: true. Actual selection will disappear.
  3. Same as № 3.
  4. Perform single left click on selected word. You will see this collapse sequence: false. But actual selection will disappear.

What happened

Notice difference between № 4 and № 6. Both of them leads to same result: selection will disappear. But with one difference: № 4 will correctly point on that selection is collapsed, but № 6 will treat disappeared selection as not collapsed. It is means that window.getSelection() will provide disappeared selection, even if there are no actual visible selection.

It is happened in both browsers: Google Chrome and Mozilla Firefox.

How to test wrong order of events

Perform all these steps with a little bit different code:

let mousePressed = false;

document.addEventListener('mousedown', () => {
    mousePressed = true;
});

document.addEventListener('mouseup', () => {
    if (mousePressed) {
        window.setTimeout(() => {
            const selection = window.getSelection();
            const isCollapsed = selection.isCollapsed;

            console.log(`Collapsed: ${isCollapsed}`);
        }, 1000);

        mousePressed = false;
    }
});

It is same, but now window.getSelection() will be called with delay. And now № 6 yields expected result: true.

Why it is happened

I do not know. I think there is something with events order for different situations. With situation № 4 order yields expected result. With situation № 6 probably order of events is different, and that order yields not expected result.

What I'm asking for

There is something wrong with my code? Or it is a bug that should be reported on Chromium and Bugzilla? Or it is actually expected behavior?

I mean, i need to achieve same result (Collapsed: true) for both № 4 and № 6.


Solution

  • The problem was solved using this pattern:

    let mousePressed = false;
    
    document.addEventListener('mousedown', () => {
        mousePressed = true;
    });
    
    document.addEventListener('mouseup', () => {
        if (mousePressed) {
            window.setTimeout(() => {
                const selection = window.getSelection();
                const isCollapsed = selection.isCollapsed;
    
                console.log(`Collapsed: ${isCollapsed}`);
            }, 0);
    
            mousePressed = false;
        }
    });
    

    i.e., timer with 0 ms delay.

    Now both № 4 and № 6 are handled correctly.

    But i'm still not sure if it is right way to handle such situations. Maybe there is something wrong with code, maybe there is bug in both browsers.