Не работает copyToClipboard js в chrome extension

299
03 октября 2021, 07:30

Создаю расширение для chrome. Столкнулся с проблемой копирования текста на js. Сам код копирования простой.

Суть проблемы в том что код не копируется в буфер обмена.

Кто то может помочь решить проблему?

Вот сам код.

function copyToClipboard() {
  /* Get the text field */
  var copyText = document.getElementById("clipboard-text");
  /* Select the text field */
  copyText.select();
  copyText.setSelectionRange(0, 99999); /*For mobile devices*/
  /* Copy the text inside the text field */
  document.execCommand("copy");
  /* Alert the copied text */
  alert("Copied the text: " + copyText.value);
}
document.getElementById('clipboard-copy').addEventListener("click", copyToClipboard);

После клика на кнопку Copy срабатывает событие но текст все ровно не копируется в буфер обмена.

Answer 1

Странно, у меня работает как в браузере так и в расширении без всяких дополнительных разрешений - причем как execCommand, так и clipboard.writeText

Версия Chrome 77.0.3865.120 (Официальная сборка), (64 бит)

В том числе, код работает и здесь

// lib.js 
function isInt(v) { 
    return typeof v === 'number' && isFinite(v) && Math.floor(v) === v 
} 
 
function randomMinMax(min, max) { 
    return Math.floor(Math.random() * (++max - min)) + min; 
} 
 
function isProperty(prop, obj, type) { 
    return typeof obj === 'object' && prop in obj && (type ? typeof obj[prop] === type : true) 
} 
 
// charGenerator.js 
function charGenerator(...arrs) { 
    let arr = [] 
 
    const testpush = (s) => { 
        if (arr.indexOf(s) === -1) { 
            arr.push(s) 
        } 
    } 
 
    const set = (s, e) => { 
        if (!isInt(s) || !isInt(e)) { 
            return 
        } 
        for (let i = s; i <= e; ++i) { 
            testpush(String.fromCharCode(i)) 
        } 
    } 
 
    let s 
    for (let i = 0; i < arrs.length; ++i) { 
        s = arrs[i] 
        if (typeof s !== 'string' || !s.length) { 
            continue 
        } 
        testpush(arrs[i][0]) 
        if (s.length > 1) { 
            set(s.charCodeAt(0) + 1, s.charCodeAt(1)) 
        } 
    } 
 
    return arr 
} 
 
// Log.js 
class Log { 
    constructor(elout) { 
        this.out = elout 
    } 
    log(mess, cl = 'log') { 
        let l = document.createElement('code') 
        l.className = cl 
        l.textContent = mess 
        this.out.appendChild(l) 
    } 
} 
 
// InputRandomText.js 
const numberChar = 8 
const abc = charGenerator('09', 'AZ', 'az') 
 
class InputRandomText { 
 
    constructor(random, input) { 
        this.input = input 
        this.buttonExec = random 
        this.buttonExec.addEventListener('click', () => { 
            this.setInput() 
        }) 
    } 
 
    static generate(length = numberChar) { 
        let l = length > abc.length ? abc.length : length 
        let arr = abc.slice() 
        let arrTo = [] 
        let i 
        while (l--) { 
            i = randomMinMax(0, arr.length) 
            arrTo.push(arr.splice(i, 1)[0]) 
        } 
        return arrTo.join('') 
    } 
 
    setInput() { 
        this.input.value = InputRandomText.generate(randomMinMax(5, numberChar)) 
    } 
} 
 
// CopyToClipboard.js 
class CopyToClipboard extends Log { 
    constructor(input, exec, log, methodExecCommand) { 
        super(log) 
        this.input = input 
        this.buttonExec = exec 
        this.methodExecCommand = methodExecCommand 
        this.write = null 
        // test 
        if (this.test()) { 
            this.buttonExec.addEventListener('click', () => { 
                this.copy() 
            }) 
        } 
        else { 
            this.log('Возможно браузер не поддерживает копирование в буфер обмена', 'error') 
        } 
    } 
 
    copy() { 
        if (/^\s+|\s+$/g.test(this.input.value)) { 
            this.input.value = this.input.value.replace(/^\s+|\s+$/g, '') 
        } 
        if (this.input.value.length) { 
            this.input.select() 
            try { 
                this.write() 
            } catch (e) { 
                this.log(e.message, 'error') 
                return 
            } 
            this.log(`Текст value.length: ${this.input.value.length} скопирован в буфер обмена`) 
            return 
        } 
        this.log('Отсутствует текст в поле ввода', 'warn') 
    } 
 
    testExecCommand() { 
        if (isProperty('queryCommandSupported', document, 'function') && document.queryCommandSupported('copy') && isProperty('execCommand', document, 'function')) { 
            this.write = () => { 
                document.execCommand('copy') 
            } 
            this.log('Копируем через document.execCommand') 
            return true 
        } 
        return false 
    } 
    testClipboard() { 
        if (isProperty('clipboard', navigator) && isProperty('writeText', navigator.clipboard, 'function')) { 
            this.write = () => { 
                navigator.clipboard.writeText(this.input.value) 
            } 
            this.log('Копируем через navigator.clipboard') 
            return true 
        } 
        return false 
    } 
 
    test() { 
        // для теста меняем порядок проверки 
        // clipboard.html 
        // clipboard.html?execCommand -> methodExecCommand 
        if (this.methodExecCommand && this.testExecCommand()) { 
            return true 
        } 
        if (this.testClipboard() || this.testExecCommand()) { 
            return true 
        } 
        return false 
    } 
} 
 
// clipboard.js 
 
document.addEventListener('DOMContentLoaded', () => { 
    new CopyToClipboard( 
        document.body.querySelector('[data-input]'), 
        document.body.querySelector('[data-exec]'), 
        document.body.querySelector('[data-log]'), 
        /execCommand/i.test(document.location.search) 
    ) 
    new InputRandomText( 
        document.body.querySelector('[data-random]'), 
        document.body.querySelector('[data-input]') 
    ) 
})
<!DOCTYPE html> 
<html lang="en"> 
 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
    <link rel="shortcut icon" href="./icons/chemistry.png" type="image/png"> 
    <title>clipboard</title> 
    <script src="./clipboard.js" type="module"></script> 
    <style> 
        body { 
            font-family: 'Courier New', Courier, monospace; 
        } 
 
        [data-log] { 
            margin-top: 1em; 
            width: 500px; 
            min-height: 100px; 
            padding: 1em; 
            background-color: #ececec; 
            border: 1px solid #9E9E9E; 
        } 
 
        [data-log]>code { 
            display: block; 
            margin-top: 1em; 
            border-left: 3px solid green; 
            padding-left: 1em; 
            font-size: 1.5em; 
        } 
 
        [data-log]>code.error { 
            border-left-color: red; 
        } 
 
        [data-log]>code.warn { 
            border-left-color: #ff9800; 
        } 
    </style> 
</head> 
 
<body> 
 
    <h1>Test Clipboard</h1> 
    <!-- Только для расширения и браузера 
    <div>Для теста меняем порядок проверки и использования доступных API</div> 
    <ul> 
        <li><a href="./clipboard.html?clipboard">clipboard.html?clipboard</a></li> 
        <li><a href="./clipboard.html?execCommand">clipboard.html?execCommand</a></li> 
    </ul> 
    --> 
 
    <button data-random>Для ленивых</button> 
    <input data-input type="text" placeholder="input text"> 
    <button data-exec>Copy</button> 
    <br><br> 
    <input type="text" placeholder="проверим ctrl+p"> Сюда просто вставляем что скопировано горячими клавишами [ctrl+p] 
    <div data-log></div> 
 
</body> 
 
</html>

Копия манифеста

{
    "manifest_version": 2,
    "name": "TestClipboard",
    "description": "TestClipboard",
    "version": "1.0.0",
    "author": "Alexander Lonberg",
    "icons": {
        "32": "icons/chemistry.png"
    },
    "browser_action": {
        "default_icon": {
            "32": "icons/chemistry.png"
        },
        "default_popup": "clipboard.html",
        "default_title": "TestClipboard"
    },
    "permissions": []
}

Можете попробовать подключить эту папку как расширение или запустить в браузере
https://yadi.sk/d/055Y4jgQCTuJWQ

UPD
опечатка ctrl+p
конечно ctrl+v

Answer 2

Убедитесь что у вас запрошено разрешение на "clipboardWrite" в разрешениях:

manifest.json:

{
    ...
    "permissions": [
        ...
        "clipboardWrite"
    ]
    ...
}

При беглом взгляде код выглядет корректно. В своем расширении я применяю достаточно схожий сниппет:

const tempEl = document.createElement('textarea');
document.body.appendChild(tempEl);
tempEl.value = textToCopy;
tempEl.select();
const copied = document.execCommand('copy');
document.body.removeChild(tempEl);
Answer 3

Предполагается использование нового Clipboard API.
С помощью него, копирование в буфер выполняется методом navigator.clipboard.writeText(), который принимает аргументом строковое значение - тот текст, который нужно скопировать.
В качестве результата возвращается Promise - то есть, выполнение асинхронное... но с другой стороны, можно удобно ловить ошибки. Да и async+await никто не отменял ;)

При вызове метода из popup'а в расширении Chrome, не требуется каких-либо дополнительных разрешений.
В расширениях Firefox они так же необязательны, но есть условие что вызов метода должен выполняться в коллбэке обработчика события, инициированного пользователем (например, в обработчике клика).

Я проверял в расширении в режиме разработчика Chrome v77, все работает исправно.

Пример, повторяющий тестовое расширение:

/* popup.js */ 
document.addEventListener('DOMContentLoaded', () => { 
  const inputEl  = document.querySelector('input'); 
  document.querySelector('button').addEventListener('click', () => { 
    navigator.clipboard.writeText(inputEl.value) 
      .then(() => { setStatus('Скопировано!'); }) 
      .catch(setStatus); 
  }); 
}); 
 
function setStatus(data) {    // эта функция только для показа результата 
  const statusEl = document.querySelector('p'); 
  if (data instanceof Error) { 
    statusEl.textContent = data.message; 
    statusEl.style.color = '#e00'; 
  } else { 
    statusEl.textContent = data; 
    statusEl.style.color = '#0c0'; 
    setTimeout(() => setStatus(''), 2e3); 
  } 
}
/* popup.html > html > head > style */ 
body { font-family: sans-serif; } 
body > * { font-size: 1rem; } 
div, p { margin: 1rem 0 0 0; padding: 0.5rem 0 0 0; border-top: 1px solid #eee; } 
textarea { display: block; width: 90%; }
<!-- popup.html > html > body --> 
<input value="Lorem ipsum"> 
<button>Скопировать в буфер обмена</button> 
<div>Сюда можно вставить текст (для проверки):</div> 
<textarea></textarea> 
<p></p>

Манифест тестового расширения:

{
  "name": "Test extension",
  "description": "Test extension",
  "version": "0.0.1",
  "manifest_version": 2,
  "browser_action": {
    "default_popup": "popup.html"
  }
}


При использовании на веб-странице (то есть, не в расширении), нужно учесть три момента:

  • по-видимому, Clipboard API работает только если страница загружена через HTTPS

  • некоторые браузеры могут требовать запрос разрешений через Permissions API

  • Clipboard API экспериментальный, а значит он не поддерживается старыми/отстающими браузерами, и (на данный момент) поддержка отсутствует в Android WebView (а в Chrome для Android - она есть)

Страница на MDN: https://wiki.developer.mozilla.org/en-US/docs/Web/API/Clipboard

READ ALSO
Как ограничить диапазон дат в bootstrap datepicker?

Как ограничить диапазон дат в bootstrap datepicker?

Использую плагин bootstrap datepicker для выбора датКак можно прописать ограничение, чтобы можно было выбрать даты в диапазоне от 18 до 100 лет?

150
Как установить русский язык в Datarangepicker?

Как установить русский язык в Datarangepicker?

Уже был подобный вопрос (Datepicker), сделал все, как там написано, подключил все библиотеки

105
Проверка на существование куки

Проверка на существование куки

Как проверить записанны ли куки в браузере, в данном коде я сначала записал куки в браузер, далее мне нужно вывести сообщение что куки записаны,...

181
Как отсортировать данный массив?

Как отсортировать данный массив?

Имеется следующий код

236