My Solution to Google Chrome & Safari Double Firing the mouseup Event when Selecting Text

I needed to write a quick bit of code today to track when a user selects any text on a page. The key was to bind a method to the “mouseup” event which would grab the selected text and do something with it. This was plain ‘ole JS and thankfully Dean Edwards and Tino Zijdel had created a nice cross-browser event handler routine which worked great. And David Walsh has a nice post on similar functionality using MooTools & jQuery so I was able to leverage some of that code that deals with grabbing the selected text:

[code lang=”js”]
function findSelected() {
if(window.getSelection) {
return window.getSelection();
}
else if(document.getSelection) {
return document.getSelection(); }
else {
var selection = document.selection && document.selection.createRange();
if(selection.text) {
return selection.text; }
return false;
}
return false;
}

function getSelectedText() {
var selected = findSelected();
if(selected && (selected = new String(selected).replace(/^\s+|\s+$/g,”))) {
alert( selected );
}
}
[/code]

From there it was just a matter of actually binding the method to “mouseup”:

[code lang=”js”]
addEvent(document,"mouseup",getSelectedText);
[/code]

All of this seemed great and it worked perfectly in Internet Explorer and Firefox but when I tried it on Chrome, I found an issue. When I would select the whole line of text an alert box would appear which was expected but when I closed the box and clicked on any whitespace in the viewport, the alert box would reappear! To see it in action, try these steps:

  1. Using Chrome or Safari, open this page: http://reybango.com/ms/test.html
  2. Using your mouse select the whole line of the text and release. An alert box should appear.
  3. Close the alert box and click on any whitespace in the viewport.

In both Chrome and Safari, you should see that a second alert box is displayed containing the selected text. This doesn’t happen in Firefox or IE. It also won’t happen if you only select part of the text. It has to be the whole line.

After quite a bit of debugging, running it by John Resig & Pete Higgins and running short on time, I decided to create a work-around which resolved the issue. By using a closure to keep a persistent copy of the last selected text, I can compare that value to the newly selected text and if it matches the persistent copy, I can ignore it. It’s a very basic way to filter out the second call being done by Chrome & Safari:

[code lang=”js”]
getSelectedText = (function() {
var selected = "";

return (function() {
var currSelection = findSelected();
if (selected != currSelection) {
selected = currSelection;
if(selected && (selected = new String(selected).replace(/^\s+|\s+$/g,”))) {
alert( selected );
}
}
})
})();
[/code]

Here’s the working demo.

Now I acknowledge that this is not the ideal solution and I need to determine what the browser inconsistency is here. It seems like it might be Webkit-related since it only affects Chrome & Safari so I’ll have to dig further into that. For now, though, the closure works and since I don’t do a circular reference between any DOM or JS objects, it looks like memory leaks shouldn’t be an issue.

Rey Bango

3 Comments

  1. Rey,
    you have setup an on “mouseup” event so it is normal it fires each time you release the mouse button. You are filtering “mouseup” to act only if a selection exists, that’s ok. Unfortunately the difference between the browsers you mention is that in Firefox the selection is cleared as soon as the next “mousedown” while in Safari/Chrome it is cleared on “mouseup” so when fired your filtering will still find a selection active on Safari/Chrome, thus the second alert you see.

    This is how you can alternatively fix that problem, just add these lines:

    function clearSelectedText() {
    if (window.getSelection) {
    window.getSelection().removeAllRanges();
    } else if (document.getSelection) {
    document.getSelection().empty();
    } else {
    var selection = document.selection && document.selection.createRange();
    if (selection.text) {
    selection.collapse(true);
    selection.select();
    }
    }
    }

    addEvent(document, “mousedown”, clearSelectedText);

    Hope this help fix it and explain what happens in Safari/Chrome, I wouldn’t mark this as a bug. Have fun.

    Diego

    • Thank you so much for looking at this Diego. Your solution worked like a charm and no need for a closure. Very awesome. I’d still be interested in knowing why Google & Safari (Webkit?) implement this behavior while IE & Firefox do it differently. It just continues to show how the lack of consensus among browser makers makes development that much harder for us. :(

Comments are closed.