Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chrome Selection.addRange() does not select (an execCommand('copy') use case)

Writing a tiny browser extension in Chrome to copy some specific text from specific web pages to the clipboard. In HTML format, so people could paste it to office programs like word, outlook etc.

document.execCommand('copy') is the command I use, it is being triggered by an document.onkeydown key combination (Alt+1), and it works fine - but only for the first time. If you try to press the key combination again, it will do nothing.

I've found the reason for it, document.queryCommandEnabled("copy") returns true for the first time, and false for any additional attempt. If I reload the page, it returns true again for the first time. Furthermore, if I click outside of the browser window after loading the page, and then click in the browser and use the key combination, is immediately returns false, even for the first time.

function copy(text) {
  var sel = document.createElement("div"); // Creating temporary holder for text to copy

  sel.style.opacity = 0;             sel.style.position = "absolute";  // These are not really required,
  sel.style.pointerEvents = "none";  sel.style.zIndex = -1;            // at least for Chrome

  sel.innerHTML = text; // Put the text to copy into the temporary holder

  document.body.appendChild(sel); // Add the temporary holder to the page

  var range = document.createRange();     // All this is required to select the text,
  range.selectNode(sel);                  // since the holder is not an editable text
  window.getSelection().addRange(range);  // field and must be treated differently.

  // document.designMode = 'on'; (Tried this, no effect)

  /* Debugging */ alert("Enabled = " + document.queryCommandEnabled("copy") + " Design mode = " + document.designMode);

  try {
    document.execCommand('copy'); // Copy to clipbopard
  }
  catch (err) {
    alert('Unable to copy');
    console.log('Unable to copy'); // Copy failed?!
  }

  // document.designMode = 'off'; (Tried this, no effect)

  document.body.removeChild(sel); // Clean up removing the temporary holder
}

document.onkeydown=function(e){
  if(e.altKey && e.which == 49) { // Alt+1
    copy(link_variable);
    return false;
  }
}

Any ideas?

Adding the manifest file:

{
  "manifest_version": 2,
  "name": "Usage text",
  "version": "0.2",
  "description": "Whatever",
  "content_scripts": [
    {
      "matches": [
        "*://some.specific.site.com/*"
      ],
      "js": ["content.js"]
    }
  ],
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {
      "name": "Whatever",
      "default_icon": "icon.png"
  },
  "permissions": [
    "tabs",
    "clipboardWrite"
  ]
}

Update:

Transferred the operation from the content script to the background script, no change.

like image 738
fault-tolerant Avatar asked Jul 13 '17 20:07

fault-tolerant


1 Answers

When trying the code from the question Chromium 59 logged this warning:

[Deprecation] The behavior that Selection.addRange() merges existing Range and the specified Range was removed. See https://www.chromestatus.com/features/6680566019653632 for more details.

That

  1. lead me to the mentioned Chrome status

    In a case where document already has text selection and Selection.addRange() is called, Blink merges the Range and the existing text selection into one if they have overlap, and does nothing otherwise. We'll change it so that Blink always ignore the Range. It matches to Edge.

    which

  2. lead me to the discussion on that W3C specification which

  3. made me read the W3C specification.

According to the specification addRange() should do nothing if there's already a selection:

  1. If rangeCount is not 0, abort these steps.

So interestingly, there seems to be a selection already. Checking window. getSelection().rangeCount confirmed this. I can't explain why this is but it's the cause of the problem mentioned in the question.

Calling removeAllRanges() before addRange() solved the issue:

var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
like image 120
try-catch-finally Avatar answered Nov 13 '22 23:11

try-catch-finally